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

时间:2015-02-01 06:57:47

标签: c++ copy-constructor virtual-functions

在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. 我有一个虚函数,所以复制构造函数是非平凡的
  3. 以下是我的问题:

    1. 由于vptr的存在,一个非平凡的复制构造函数与一个微不足道的复制构造函数有何不同?
    2. 为什么不能复制vptr?如果两个相同类型的对象(继承中的级别相同),它们都可以指向相同的vtable,它们可以不是吗?
    3. 由于我已经定义了自己的复制构造函数,编译器是否会添加&#39;我的复制构造函数的特殊指令来处理虚拟性?
    4. 干杯。

2 个答案:

答案 0 :(得分:5)

  

由于vptr的存在,非平凡的复制构造函数与普通复制构造函数有何不同?

vptr不是从对象复制的,但必须初始化为指向目标类的虚拟表。因此,直接&#34; memcpy&#34;无法从源复制到目的地。

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

  

为什么不能复制vptr?如果两个相同类型的对象(继承中的级别相同),它们都可以指向相同的vtable,它们可以不是吗?

这里的棘手问题是假设你做了那个&#34;两个相同类型的对象&#34;。在一般情况下不是这样。源对象很可能是某个其他派生类,因此具有不同的虚拟表指针。另一个(更罕见的)实际问题与跨模块代码(在不同的DLL / so文件或可执行文件中)有关,其中两个相同类型的对象可能使用不同的虚拟表(例如,有一些极端情况或hackish代码这可能不会破坏ODR,就像不同的模板实例一样。)

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

  

由于我已经定义了自己的复制构造函数,编译器是否会添加&#39;我的复制构造函数的特殊指令来处理虚拟性?

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

答案 1 :(得分:2)

我认为唯一最重要的障碍是切片。复制构造函数接受要复制的对象的const 引用,并且该引用可能绑定到派生类。如果没有虚拟基础,没有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;
}

关于你的问题

  

由于我已经定义了自己的拷贝构造函数,编译器是否“向”我的拷贝构造函数“添加”特殊指令来处理虚拟性?

这对复制构造函数来说并不特殊。在输入任何构造函数的主体之前,实现将确保构造所有基础对象和任何非平凡的数据集合。因此,如果您编写一个复制构造函数,它将会看到一个半构造的*this对象(如果是具有虚函数成员的类型),vptr设置为的类型目前构建的类<​​/ em>。最后一部分是强调的,因为vptr将在构造期间从基础更改为大多数派生因为调用各种构造函数。