Effective C++:Chapter 2 Note

All notes are my personal thoughts on the Book Effective C++ Third Edition by Scott Meyers. They do not represent the opinions of any affiliations.

C++ Class Default Generated Code

Normally, if users do not define a constructor or destructor function, the compiler would automatically add one that has specific functionality.

class Example{
    /* These are generated by compilers
    Example(){
        
    }
    ~Example(){
    
    }
    */

    void func();

};

However, if users do define, the compiler will then not add one. The compiler would also generate copy constructor and copy assignment operator by default. It is interesting to note that the copy assignment operator would not be automatically added if the condition does not allow it. The author gave one example of a class that has a data member of reference. Since C++ does not allow changing references, the copy assignment operator will not be used. Other instances might be something like the inclusion of unique_ptr or shared_ptr.

We can also disallow compiler-generated functions. The reason for doing this may be varied. For example, unique_ptr disallowed copy-assignment operator to ensure that it always has the ownership of the data. The author achieved the result by defining the function as private member functions. However, in C++ 11, a new feature was added to achieve this result: delete. Now, we can disallow functions by declaring it as deleted. The following example illustrated that.

class Example{
    Example() = default;
    ~Example() = default;
    Example& operator=(const A&) = delete;  // Disallowing copy-assignment operator
    Example(const Example&) = delete;   // Disallowing copy-constructor
};

Destructors

When dealing with inheritance, having a correct destructor is important to properly delete the items. However, when a base class has a non-virtual destructor function, a pointer to its derived class would be deleted using the base class destructor function. The result would be undefined. It was highly likely that the resource would be leaked. The solution to this problem is to define the destructor as virtual.

class Base{
    Base();
    virtual ~Base(); // Defining the destructor as virtual solves the problem
};

Thinking backward, if a base class has no virtual destructor, it would probably not supposed to be used as a base class (all STL containers for example). Furthermore, always making a destructor virtual is a bad idea since it would increase the size of objects.

Destructors should also never emit any exceptions. Exceptions should either be caught and swallowed, or the program should be terminated. Moreover, never call virtual functions in constructor and destructor. Try the following code.

class A{
public:
    A(){
        out();
    }
    virtual void out(){
        std::cout<<"This is A."<<std::endl;
    }
    ~A(){
        out();
    }
};

class B : public A{
public:
    B(){
        out();
    }
    virtual void out(){
        std::cout<<"This is B."<<std::endl;
    }
    ~B(){
        out();
    }
};

class C : public B{
public:
    C(){
        out();
    }
    virtual void out(){
        std::cout<<"This is C."<<std::endl;
    }
    ~C(){
        out();
    }
};

int main()
{
    A a; // Will print This is A
    B b; // Will print Both A and B
    C c; // Will print A, B, and C
    std::cout<<std::endl; // Seperate Output
    // The destruction would print the above in reverse order.
    return 0;
}

The code above will show that the calling of a virtual function in constructor would be based on the currently constructing type. The construction of the object of type C would result in a call of all three different out() function.

Assignment

Let the assignment operator always return a pointer to this. This is a convention. It allows a chain of assignments.

class Foo{
    Foo& operator=(const Foo&){
        ...
        return *this;
    }
};

Furthermore, always try to handle an assignment to self when coding the assignment operator. One way to achieve it is to use the “copy and swap” technique. However, it sometimes sacrifices the efficiency needed by the program. This is something that needs to be considered.

Copying Objects

The thing that the author wants us to know is to remember to copy objects entirely. When writing a copy assignment operator for a derived class, it is common that people forget to copy the objects in its base class. Hence, remembering to call the copy-assignment operator function for the base-class is also important.

class Base{
public:
    ...
    Base& operator=(const Base&); // Define Base copy-assignment operator
private:
    // Some data
};

class Derived : Base{
public:
    ...
    Derived& operator=(const Derived& oth){
        Base::operator=(oth);
        // Perform Derived Data Copying
    }
    ...
};

The above code ensures that the data within the base class is also copied.

PS

Way to go. Currently in quarantine, locked within a hotel. Probably more time to post. (^▽^)

 

 

 

Leave a Reply

Your email address will not be published.