非平凡的复制构造函数有什么作用?

What does a non-trivial copy constructor do?

在 C++ 中,如果未定义复制构造函数,编译器将为您完成。如果定义了一个,编译器就不会。编译器生成的复制构造函数可以是平凡的或非平凡的。在普通的复制构造函数中,它执行成员复制。就是这样。

但是,如果有一个虚函数,复制构造函数是不平凡的。它不能只是按位复制。

这是我的程序。没有什么特别的。只是为了说明我的观点..

#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;
}

在这个程序中,

  1. 我定义了一个拷贝构造函数
  2. 我有一个虚函数,所以复制构造函数很重要

这是我的问题:

  1. 由于存在 vptr,非平凡的复制构造函数与平凡的复制构造函数有何不同?
  2. 为什么不能复制vptr?如果两个相同类型的对象(在继承中的相同级别),它们都可以指向同一个 vtable,他们不能吗?
  3. 由于我定义了自己的复制构造函数,编译器是否'add' 对我的复制构造函数有特殊说明来处理虚拟性?

干杯。

我认为最重要的障碍是切片。复制构造函数接受对要复制的对象的 const 引用 ,并且该引用可能绑定到派生的 class。如果没有虚拟基础,没有 vptr 也没有非平凡可复制的数据成员,复制构造函数可以实现为

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

因为即使参数绑定到 派生 来自 Foo 的对象,它的前 sizeof(Foo) 个字节将是一个完整的 Foo 反对 之后加入任何其他成员。但是,如果有一个 vptr——可能是第一个成员——它必须像这样实现

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

关于你的问题

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

这对复制构造函数来说并不特殊。在进入任何构造函数的主体之前,实现将确保所有基础对象和任何非平凡的数据成员都将被构造。因此,如果您编写一个复制构造函数,它将已经看到一个半构造的 *this 对象,其中(在具有虚函数成员的类型的情况下) vptr 设置 为该类型当前构建的 class。强调最后一部分是因为 vptr 会在构造过程中随着各种构造函数的调用而从基础变为最派生。

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

vptr 不是从 source 对象复制的,而是必须初始化为指向 destination[=] 的虚拟 table 25=] class。因此,从源到目标的直接 "memcpy" 复制是不可能的。

此外,请记住,拥有 vptr 并不是严格的要求,兼容的实现可以通过其他方式实现虚拟调度(我不知道那会是什么)。虽然,据我所知,所有实现都使用这种机制。但是无论实现选择哪种方式做事,很明显会有一些信息,如 vptr,必须以某种方式设置,这与直接 "memcpy" 副本不兼容。

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?

这里棘手的问题是您所做的假设 "both objects of same type"。这在一般情况下是不正确的。源对象很可能是其他派生的 class,因此具有不同的虚拟 table 指针。另一个(更罕见的)实际问题与跨模块代码(在不同的 DLL/so 文件或 executable 中)有关,其中两个相同类型的对象可能使用不同的虚拟 table s(例如,有一些极端情况或骇人听闻的代码,在不破坏 ODR 的情况下这是可能的,例如不同的模板实例化)。

但关键是编译器无法保证对于复制构造函数的任何使用,仅从源对象复制 vptr 并期望它适合目标是安全的对象。

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

是的。确实如此。我不记得确切的规范,但基本上要求是,当您点击构造函数的主体时,vptr(或用于动态调度的任何其他机制)已初始化。这实质上要求编译器在所有用户定义的构造函数中添加代码以隐式初始化 vptr。