What does a non-trivial copy constructor do? [duplicate]

泪湿孤枕 提交于 2021-01-28 03:17:07

问题


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.


回答1:


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.




回答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.



来源:https://stackoverflow.com/questions/28260322/what-does-a-non-trivial-copy-constructor-do

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!