2

In C++, if a copy constructor is not defined the compiler will do that for you. If one is defined, compiler would not. The compiler generated copy constructor can be trivial or non-trivial. In a trivial copy constructor it does a member-wise copy. That's it.

However, if there is a virtual function, the copy constructor is non-trivial. It cannot just to bit-wise copy.

So here is my program. There is nothing special. Just to make my point..

#include<iostream>
using namespace std;

class baseClass
{
public:
    int var;
    int* varPtr;
    const float floatVar;
    int &intRefVar;

    baseClass(int value) : var(value), varPtr(&var), floatVar(value), intRefVar(var)
    {
        cout << "baseClass constructor" << endl;
    }

    baseClass(const baseClass& objToCopy) : var(objToCopy.var), varPtr(&var), floatVar(objToCopy.floatVar), intRefVar(var)
    {
        cout << "baseClass copy constructor" << endl;
    }

    virtual void func()
    {
        cout << "Just a virtual func." << endl;
    }
};

class derivedClass : public baseClass
{
public:
    derivedClass(int value) : baseClass(value)
    {
        cout << "derivedClass constructor" << endl;
    }

    derivedClass(const derivedClass& objToCopy) : baseClass(objToCopy)
    {
        cout << "derivedClass copy constructor" << endl;
    }

    virtual void func()
    {
        cout << "Just another virtual func." << endl;
    }
};


int main(int argc, char** argv)
{
    derivedClass derClassObj1(10);
    derivedClass derClassObj2(derClassObj1);
    return 0;
}

In this program,

  1. I have defined a copy constructor
  2. I have a virtual function so the copy constructor is non-trivial

Here are my questions:

  1. How does a non-trivial copy constructor differ from a trivial one due to the presence of a vptr?
  2. Why cannot the vptr be copied? If both objects of same type (same level in the inheritance), they both can point to the same vtable, can they not?
  3. Since I have defined my own copy constructor, does the compiler 'add' special instructions to my copy constructor to handle the virtualness?

Cheers.

madu
  • 5,232
  • 14
  • 56
  • 96

2 Answers2

5

How does a non-trivial copy constructor differ from a trivial one due to the presence of a vptr?

The vptr is not copied from the source object, but has to be initialized to point to the virtual table of the destination class. Therefore, a straight "memcpy" copy from source to destination is not possible.

Also, keep in mind that having a vptr is not strictly a requirement, a compliant implementation could implement virtual dispatching some other way (I don't know what that would be though). Although, AFAIK, all implementations use this mechanism. But whichever way an implementation chooses to do things, it is clear that there will be some piece of information like a vptr that will have to be set in some way that is incompatible with a straight "memcpy" copy.

Why cannot the vptr be copied? If both objects of same type (same level in the inheritance), they both can point to the same vtable, can they not?

The sticky issue here is that assumption you made that "both objects of same type". This is not true in the general case. The source object could very well be of some other derived class and therefore have a different virtual table pointer. Another (more rare) practical issue has to do with cross-modular code (in different DLL/so files or executables), where two objects of the same type might use different virtual tables (e.g., there are some corner cases or hackish code in which this is possible without breaking ODR, like different template instantiations).

But the point is that there is no way that the compiler can be assured that for any use of the copy-constructor it is safe to just copy the vptr from the source object and expect it to be appropriate for the destination object.

Since I have defined my own copy constructor, does the compiler 'add' special instructions to my copy constructor to handle the virtualness?

Yes. It does. I don't remember the exact specification, but basically the requirement is that by the time you hit the body of the constructor, the vptr (or whatever other mechanism used for dynamic dispatch) is initialized. That essentially requires the compiler to add code to implicitly initialize the vptr, in all user-defined constructors.

Mikael Persson
  • 18,174
  • 6
  • 36
  • 52
  • An alternate means of doing virtual function dispatch include a jump table for each virtual function call, selecting a different function based on actual object type. This would be markedly less efficient (both in amount of code and performance) than a vtable. You're incorrect that the vptr (or whatever) is set up before a constructor is invoked. It is set up after the constructor of the most derived class is invoked, which in turn is after all base class constructors. That is why invoking a virtual function from a constructor does not do virtual function dispatch. – Rob Feb 01 '15 at 07:29
  • @Rob It is set up for each class as its constructor is run. During construction, the type of an object “evolves” as its sob-objects are constructed. – 5gon12eder Feb 01 '15 at 07:34
  • 1
    @Rob I didn't say that the vptr is set up before the constructor is invoked, I said before the **body of the constructor**. Each class will set the vptr to its own vtable as it enters its body (and thus, after its base-class has been constructed). The last constructor body to be encountered is the one from the most derived class, and that's how that vptr is the one that "sticks". And it is perfectly safe to call a virtual func in a constructor, it just won't be dispatched to the most derived class (not yet constructed), that's all. – Mikael Persson Feb 01 '15 at 07:37
2

I think the single most important obstacle is slicing. The copy constructor accepts a const reference to the object to be copied and that reference might be bound to a derived class. If there are no virtual bases, no vptr and no non-trivially copyable data members, the copy constructor could be implemented as

Foo(const Foo& o) noexcept
{
  std::memcpy(this, &o, sizeof(Foo));
}

because even if the argument is bound to an object that is derived from Foo, its first sizeof(Foo) bytes will be a complete Foo object with any additional members coming after that. However, if there is a vptr – possibly as the very first member – it must be implemented like so

Foo(const Foo& o) noexcept
{
  std::memcpy(this, &o, sizeof(Foo));
  this->__vptr = Foo::__vptr;
}

Regarding your question

Since I have defined my own copy constructor, does the compiler “add” special instructions to my copy constructor to handle the virtualness?

This is not special to the copy constructor. Before any constructor's body will be entered, the implementation will have made sure that all base objects and any non-trivial data mebers will be constructed. So if you write a copy-constructor, it will already see a semi-constructed *this object with (in case of a type with virtual function members) the vptr set to the type of the currently constructed class. The last part is emphasized because the vptr will change during construction from base to most derived as the various constructors are called.

5gon12eder
  • 24,280
  • 5
  • 45
  • 92