[note] Effective C++ Chapter 2

Effective C++ Learning Note 2
(undergradute edition)

Constructors, Destructors, Assignment operators.

===========================================

Knowing the Default Functions

  • a copy constructor
  • a copy assignment operator
  • a destructor
  • (if not declared) a constructor

all of them are inline and public.

Constructor

a place for invocation of the ‘constructors’ of its base-class and non-static members.(quite similar to destructors)

Destructors

aside from what we have said in the “constructors”, ‘destructor’ has virtual-or-not distribution.

  • default: non-virtual
  • unless: base-class’s destructor is virtual

copy functions

one-by-one call the copy cosntructors or copy assignment operators.

if any of the functions are prohibited, the copy would fail.

in fact, the condition is not too hard to get.

disallow auto-copy

  • private copy functions(ios_base, basic_ios, sentry, etc all use the technique)
  • copy funtion = delete
  • derived from uncopyable base-class

Virtual Destructors

  • Polymorphic base classes(if one has any virtual functions) should declare virtual destructors.
  • (that’s to say) Classes not designed to be base classes or not designed to be used polymorphically should not declare virtual destructors

About second point: (a small class for example), pointers in virtual table means a great explosion about its cost, but in fact, its performance didn’t improve at all. (this rule is quite different from that in the ‘const’ problem. )

Destructors and Exceptions

If the destructing process goes wrong, we will then be put into a dilemma:

  • re-alloc the resources?
  • save all to betterly deal with the exception?

especially when the exception piles. it’ll then ambiguous for us to catch and debug.

two possible solutions:

  • Terminating: call std::abort to forstall undefined behavior
  • Swallowing: (if not fatal) swallow the exception in the destructor (and print logs)

“Shirk”?

example in book’s page 47:

class DBConn
{
    void close();//for client, to whom we give a chance to remedy in advance.
    {
        db.close();
        closed = true;
    }
    ~DBConn()//if client look down upon us, huh! GO OUT AND DEBUG!
    {
        if (!closed)
        try{
            db.close();
        }
        cathc(...){
            //make log entry
        }
    }
}

sum-up:: If class clients need to be able to react to exceptions thrown during an operation, the class should provide a regular(i.e., non-destructor) function that performs the operation.(NOT IN DESTRUCTOR)

Virtual Functions Never (should be) in constructors or a destructor

A little thought, and we’ll find the reason behind the title:

  • when a base-class is being constructed, its polymorphic technique hasn’t started yet.
  • while it’s being destructed, its derived classes have all been destructed.

constructors are sometimes have much in common, it’s a good engineering to encapsule the common parts in one init() function.

sum-up: To behave polymorphically(log where the base class is derived and constructed) in the base-class’s con/de, we cannot call from the base-side, but there is another way to compensate. We could pass the parameters from sons to base.

A convention to have assignment operators return reference-to *this

it’s just a convention. By returning a reference, we may use assignment chain like this:

x = (y = (z = 15));

This convention applies to all assignment operators, not just the standard form shown above. Hence:

class Widget {
public:
...
Widget& operator+=(const Widget& rhs // the convention applies to
{                                   // +=, -=, *=, etc.
    ...                             // it applies even if the
    return *this;                   // operator's parameter type
}                                   // is unconventional

not allowing the convention, it won’t CE, but it’s been followed widely by built-in types and STL.

Assignment to-self with operator=

It’s not illegal, so be careful: though obvious assignment is not that probable.

aliasing

references or pointers to multiple objects of the same type needs to consider that the objects might be the same.
In a polymorphic hierarchy, same type is not even necessary.

risks

// unsafe impl. of operator=
Widget& Widget::operator=(const Widget& rhs) 
{
delete pb; // stop using current bitmap
pb = new Bitmap(*rhs.pb); // start using a copy of rhs's bitmap
return *this;
}

When *this and rhs are the same object, the delete destroys the bitmap of both.

solutions

There are two problems to solve: assign-to-self safety and exception safety

identity test

Widget& Widget::operator=(const Widget& rhs)
{
if (this == &rhs) return *this; // identity test: if a self-assignment, do nothing
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}

It solves our concerns about self-assignment safety, but is still exception-unsafe.

a modified version

we just have to be careful not to delete pb until after we’ve copied what it points to:

Widget& Widget::operator=(const Widget& rhs)
{
Bitmap *pOrig = pb; // remember original pb
pb = new Bitmap(*rhs.pb); // make pb point to a copy of *pb
delete pOrig; // delete the original pb
return *this;
}
Now

HOWEVER, exception-safety always renders self-assignment safety, thus, we have the:

copy and swap

Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs); // make a copy of rhs's data
swap(temp); // swap *this's data with the copy's
return *this;
}

when pass vals, we have a itself-temporary technique: by-value, we can even simplify the operator as:

Widget& Widget::operator=(Widget rhs) // rhs is a copy of the object
{ // passed in note pass by val
swap(rhs); // swap *this's data with
// the copy's
return *this;
}

Copy all parts of an Object

whether a copy constructor or copy assignment operator (they’re all called copying functions)

When you declare your own copying functions, you are indicating to compilers that there is something about the default implementations you don’t like (this is truly an interesting point which indicate why we’re allowed to customized our copying functions). Compilers seem to take offense at this, and they retaliate in a curious fashion: they don’t tell you when your implementations are almost certainly wrong.

in common classes

just need to remember not to omit anyone

in derived class

a possible version of invoking base copying functions.

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
: Customer(rhs), // invoke base class copy ctor
priority(rhs.priority)
{
logCall("PriorityCustomer copy constructor");
}

be careful to how we invoke the base-copy

PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
logCall("PriorityCustomer copy assignment operator");
Customer::operator=(rhs); // assign base class
parts
priority = rhs.priority;
return *this;
}

code reuse—by init()

the two copying functions always have lots of similarities.

But they’re essentially compatible. One is used for already-existed obj, another is for obj hasn’t.

So when reusing, we need to encapsule the common parts into a private function (which is conventionally) named init()

e.g. in this case, however, we only have a line common, thus unnecessary to declare one.

原创文章 42 获赞 17 访问量 1521

猜你喜欢

转载自blog.csdn.net/weixin_45502929/article/details/105523522