考虑这段代码:
#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 :(得分: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行:vector v construct
第2行:构建Base b(调用Base的构造函数)
第3行:构造子c(调用Child的构造函数和Base的构造函数)
第4行:v是最大容量的当前值,需要调整大小。
内存由v分配给Base的1个元素
Base b复制到v [0](调用Base的复制构造函数)。
第5行:v再次处于最大容量,需要调整大小。
内存由v分配给Base的2个元素
旧的v [0]被复制到新的v [0]中(调用Base的复制构造函数)。
删除旧的v [0](调用Base的析构函数(“Base destructor:b”))。
将子c复制到v [1](调用Base的复制构造函数)。
第6行:c,b和v超出范围。
删除子c(调用Child的析构函数(“Child析构函数”),然后删除Base的析构函数(“Base destructor:c”)。
基数b被删除(调用Base的析构函数(“Base析构函数:b”))。
删除Base v [0],v [1](调用Base的析构函数两次(“Base destructor:b”,“Base destructor:c”))。
没有内存泄漏 - 对于上面序列中的每个构造函数,都会调用相应的析构函数。
此外,您似乎对复制构造函数非常困惑。子c被传递给push_back作为Base&amp; - 然后按预期调用Base的复制构造函数。由于Base的隐式复制构造函数不是虚拟的或覆盖的,因此让Child从uncopyable派生不会改变它。
请注意,vector<Base>
无法存储Child类型的对象;它只知道为Base分配足够的内存。将Child实例分配给Base时发生的事情称为切片,虽然经常是无意识和误解,但在您描述的场景中看起来可能实际上是您想要的。
答案 1 :(得分:3)
每当我尝试复制Child类型的对象时,我都希望得到编译时错误。
您没有复制Child
个对象。将Child
c
放入vector<Base>
后,只会复制Base
。它与执行b = c;
基本相同。如果您复制/分配Child
,则会收到错误。
Child d = c; // compile error
默认的复制构造函数将调用任何基类和成员对象的复制构造函数,并对基元和指针执行按位复制。
答案 2 :(得分:0)
编辑:答案是错误的......目前正在编辑以获得更好的响应。
为什么Base(具有类型b)的析构函数被调用三次而不是两次(我们是否有两个以上的对象b副本)?
很可能是Vector正在制作b
的副本。矢量经常这样做。
当我们复制Child类型的对象时会发生什么,考虑到其父类之一的copy-constructor是私有的。是不确定的行为?
没有。 C的拷贝构造函数将调用基类的拷贝构造函数。因此,如果基类复制构造函数是私有的,则它将无法编译。
每当我尝试复制Child类型的对象时,我都需要获得编译时错误,同时允许复制Base类对象。最好的方法是什么?
为Child声明一个私有的复制构造函数,如下所示:
private:
Child(const Child& a) {
throw "cannot make a copy";
}
每当客户端尝试复制Child对象(调用Child的析构函数而不调用Base的析构函数)时,所需的行为就是丢弃子数据。
不确定你的意思。复制构造函数意味着创建一个新对象。您无法对(旧)对象执行操作。