Tuesday, November 7, 2006

C++: I have heard that if I don't write one or more of default constructor, copy constructor, and copy assignment operator the compiler creates them.

Ha! They have taken you half-way through your voyage. This is incomplete information. The answer is 'NOT ALWAYS'! Surprised? Well then read on...

Eligibility: You should be familiar with basic formulation of the C++ language. You can check for 'The C++ Standard' here.


Triviality...

The C++ Standard states that the compiler should create the default .ctor, copy .ctor, and copy assignment operator if they are non-trivial. Now we need to consider the conditions in which the three are non-trivial. Let's consider them one by one.

The Default Constructor

You should remember that if you provide other argument constructors but not the default constructor, then the default constructor is not prepared for you. The compiler assumes that this is the exact behavior you needed. The standard states that the default constructor is non-trivial if:

  1. Any of the member class objects has a default constructor.
  2. Base class has a default constructor.
  3. Class has virtual functions.
  4. Class has virtual base class.
If any of these conditions is not met, then the compiler doesn't create a constructor for you. So, if we consider the following code

class MyClass{
    public:
        void SomeFunction( ) {//...}
    private:
        int* somedata;
        int _x, _y;
};

the default constructor is not generated and the members are left uninitialized. This may result in unwanted behavior. Here, somedata may point to restricted memory and it's access may result in a program crash. So, it's best to define a default constructor, and should be made a habit.


The Copy Constructor

The Standard states that the copy constructor if not defined should be generated only if it will not be trivial. It goes on to add that the condition of being trivial is to exhibit bitwise copy semantics. Bitwise copy semantics mean bit-by-bit copy of an object. The standard states that a class does not exhibit bitwise copy semantics, if:

  1. When the class contains a member object of a class for which a copy constructor exists (either explicitly declared by the class designer, or synthesized by the compiler).
  2. When the class is derived from a base class for which a copy constructor exists (again, either explicitly declared or synthesized).
  3. When a class declares one or more virtual functions. This is essential for proper initialization of vptr if the parent object is initialized with an object of derived class.
  4. When the class is derived from an inheritance chain in which one or more base classes are virtual. This is essential to properly initialize the virtual class subobject if the parent object is initialized with an object of derived class. Failing to which would result in improper initialization of virtual base class pointer/offset within the vtbl.
So, the following class doesn't create a copy constructor for you. That is, there is no production of a function (and it's call). Rather, a code is inlined wherever needed.

class MyClass {
    public:
        MyClass( ):name(0), x(0){ }
        MyClass (const char* );
        ~MyClass( ) {
            delete[] name ;
        }
        void ShowMe( ) { //... }
    private:
        char *name;
        int x;
};

Now if we write

MyClass M;
MyClass M2 =M; //invoke inlining
someFunction(M2); //invoke inlining

or

void someOtherFunction() {
    MyClass M;
    return M;//invoke inlining
}

There is no functional call, but, the code for bit to bit copy of members is inlined. That is,

M2.name = M.name;
M2.x=M.x;

This will lead to both the names pointing to the same location. So, if any object changes or deletes (while exiting the scope) it's name, it will result in a dangling pointer in other object as the memory pointer but it's name would have been invalidated.

So, it is advised to always declare a copy constructor. But if you are smart and know that it won't lead to bugs, you can give it a miss. This will increase program speed as there won't be any functional jumps.


Copy Assignment Operator

The condition for synthesizing copy assignment operator is the same as that of the copy constructor -- that it is should not exhibit the bitwise copy semantics, i.e should be non-trivial. The conditions that a copy assignment operator does not show bitwise copy semantics are:

  1. When the class contains a member object of a class for which a copy assignment operator exists (either explicitly declared by the class designer, or synthesized by the compiler).
  2. When the class is derived from a base class for which a copy assignment operator exists (again, either explicitly declared or synthesized).
  3. When a class declares one or more virtual functions. This is essential for proper initialization of vptr if the parent object is initialized with an object of derived class.
  4. When the class is derived from an inheritance chain in which one or more base classes are virtual. This is essential to properly initialize the virtual class subobject if the parent object is initialized with an object of derived class. Which would result in improper initialization of virtual base class pointer/offset within the vtbl.

So, if the operator is trivial, it results in inlining of code, rather than resulting in function calls. This increases the speed. And as the assignment operator is used widely in a program, it is best advised not to declare one unless is necessary, like when the bitwise copy can result in dangling pointers. If you are not sure about the behavior then you should go ahead and declare one anyhow.

Now you know the full information. So bon vo‧yage o reader.

No comments: