2012-05-19 210 views
6

考虑这段代码:拷贝构造

#include <vector> 
#include <iostream> 
using namespace std; 

class Base 
{ 
    char _type; 
public: 
    Base(char type): 
     _type(type) 
    {} 

    ~Base() { 
     cout << "Base destructor: " << _type << endl; 
    } 
}; 

class uncopyable 
{ 
    protected: 
     uncopyable() {} 
     ~uncopyable() {} 
    private: 
     uncopyable(const uncopyable&); 
     const uncopyable& operator=(const uncopyable&); 
}; 

class Child : public Base, private uncopyable 
{ 
    int j; 
public: 
    Child(): 
     Base('c') 
    {} 
    ~Child() { 
     cout << "Child destructor" << endl; 
    } 
}; 


int main() 
{ 
    vector<Base> v; 
    Base b('b'); 
    Child c; 

    v.push_back(b); 
    v.push_back(c); 
    return 0; 
} 

我的系统上的输出是:

Base destructor: b 
Child destructor 
Base destructor: c 
Base destructor: b 
Base destructor: b 
Base destructor: c 

我的问题是:

  • 为什么是Base(类型b)的析构函数被调用三次而不是两次(我们是否有超过两个对象b的副本)?

  • 当我们复制一个类型为Child的对象时,考虑到其父类的副本构造函数是私有的,会发生什么情况。它是不确定的行为?

  • 无论何时我试图复制Child类型的对象,我都希望得到编译时错误。我认为孩子的默认拷贝构造函数会尝试调用Uncopyable类的私有拷贝构造函数并导致编译错误。为什么它不给编译错误?

代码被这样设计的原因是因为Child类是巨大的。

当客户端试图复制一个Child对象(调用Child的析构函数而不调用Base的析构函数)时,所需的行为是抛弃子数据。

这段代码实现了这一点,但我想它会导致未定义的行为并且有内存泄漏(从未调用复制实例的Child的析构函数)。

+0

你想要一个'编译时'错误还是你想扔掉孩子的数据?这些是相互矛盾的陈述,除非我错过了一些东西。 – Chip

+0

@Chip修复它。谢谢。 –

回答

9

这里是你的代码会发生什么:

int main() 
{ 
    vector<Base> v; // 1 
    Base b('b');  // 2 
    Child c;   // 3 

    v.push_back(b); // 4 
    v.push_back(c); // 5 
    return 0; 
}      // 6 
  1. 线1:向量v构建

  2. 线2:碱B构成的(调用Base的构造函数)

  3. 线3:构建Child c(调用Child的构造函数和Base的构造函数)

  4. 第4行:v是最大容量时的电流,需要调整大小。
    内存分配给基地的一个元素由v。
    基地b复制到v [0](调用基地的复制构造函数)。

  5. 第5行:v再次处于最大容量并需要调整大小。
    内存分配给基地的2个元素由v。
    旧的v [0]被复制到新的v [0](调用Base的复制构造函数)。
    旧的v [0]被删除(调用Base的析构函数(“Base destructor:b”))。
    子c被复制到v [1]中(调用Base的复制构造函数)。

  6. 第6行:c,b和v超出范围。
    Child C级被删除(主叫儿童的析构函数( “儿童析构函数”),那么基地的析构函数( “基本析构函数:C”。)
    碱B被删除的(调用Base的析构函数( “基本析构函数:B”))
    Base v [0],v [1]被删除(调用Base的析构函数两次(“Base destructor:b”,“Base destructor:c”))。

没有内存泄漏 - 对于上述序列中的每个构造函数,调用相应的析构函数。

此外,你似乎对复制构造函数非常困惑。子c被传递给push_back作为基地& - 然后按预期调用Base的复制构造函数。由于Base的隐式拷贝构造函数不是虚拟的或被覆盖的,因此Child从不可拷贝派生而来并不会改变它。

请注意,vector<Base>永远不能存储Child类型的对象;它只知道为Base分配足够的内存。将一个Child实例分配给一个Base时出现的情况称为分割,虽然这种分割通常是无意识和误解的,但它似乎可能实际上是您所描述场景中的所需。

+0

谢谢。我也发现这有用:http://stackoverflow.com/questions/274626/what-is-the-slicing-problem-in-c –

0

编辑:答案是错误的..当前编辑到更好的响应。

为什么基地(与类型b)的析构函数调用三次而不是两次(我们有超过两个对象b的副本)?

最有可能的是,载体是制作b副本。矢量经常这样做。

当我们复制Child类型的对象时,考虑到其父类之一的副本构造函数是私有的,会发生什么。它是不确定的行为?

否.C的复制构造函数将调用基类的复制构造函数。所以如果基类拷贝构造函数是私有的,它将不会被编译。

无论何时我尝试复制类型为Child的对象,并允许复制基类对象时,我都需要获取编译时错误。什么是最好的方式来做到这一点?

声明的私人拷贝构造函数的孩子,就像这样:

private: 
    Child(const Child& a) { 
     throw "cannot make a copy"; 
    } 

要求的性能扔掉每当客户端尝试复制的子对象的子数据(调用儿童的析构函数而不需要调用Base的析构函数)。

不确定你的意思。复制构造函数意味着创建一个新对象。您无法对(旧)对象执行操作。

+0

1.恕我直言,应该复制一次。不是两次。 2.恕我直言孩子的隐式复制构造函数应该调用父级的复制构造函数。 http://stackoverflow.com/questions/9178204/why-does-the-implicit-copy-constructor-calls-the-base-class-copy-constructor-and 3.它不工作(你可以试试它)。因为该对象首先被输入到父级,然后被复制。 –

+0

在建议的代码中,我们正在创建一个新的Child,而不是复制数据。它击败了不再创造新对象的目的。 –

+0

我想我错过了一些东西。从另一个对象创建对象时,将调用复制构造函数。这就是复制构造函数的作用...你能解释你想做什么吗? – Chip

3

每当我尝试复制Child类型的对象时,我都希望得到编译时错误。

您并未复制Child对象。当您将Childc设置为vector<Base>时,只有Base被复制。这与执行b = c;基本相同。如果您复制/分配Child,则会出现错误。

Child d = c; // compile error 

默认的拷贝构造函数会调用任何基类和成员对象的拷贝构造函数和原语和指针做一个按位复制。

+0

谢谢。这似乎是正确的。那么额外调用Base的析构函数呢? –

+1

@Shayan,它可能是一个额外的副本向量<>,而重新分配其存储。不同的vector <> impls可能在分配时表现不同。 – xan