为什么对象副本构造和析构两次?
Why is object copy constructed and destructed twice?
为什么下面的代码会出现"extra"对拷贝构造函数和析构函数?
当叮乐东的构造函数将STL容器作为参数时会发生这种情况(我试过std::vector和std::list)。它可能发生在其他事情上?如果构造函数改为采用指针,则不会发生这种情况。如果我在堆上分配 ding 也不会发生( Dingledong* ding = new Dingledong(v) )。
#include <list>
#include <iostream>
class Dingledong{
public:
Dingledong(std::list<int> numbers)
{
std::cout << "construction\n";
numbers_ = numbers;
}
Dingledong(Dingledong const& other)
{
std::cout << "COPY construction\n";
}
~Dingledong()
{
std::cout << "destructed\n";
// I would do some cleanup here.. unsubscribe from events, whatever..
// but the destructor is called sooner than I would expect and thus
// the cleanup is done prematurely.
}
std::list<int> numbers_;
};
void diller()
{
std::cout << "diller started.. " << std::endl;
std::list<int> v = std::list<int>(34);
// Having an STL container as parameter in constructor causes Dingledong's copy constructor to
// be used to create a temporary Dingledong which is immediately destructed again. Why?
Dingledong ding = Dingledong(v);
std::cout << "Has ding been destructed?\n";
}
int main()
{
diller();
system("pause");
}
输出:
diller started...
construction
COPY construction // I didn't expect this to happen
destructed // I didn't expect this to happen
Has ding been destructed?
destructed
提前致谢!
此代码:
Dingledong ding = Dingledong(v);
表示:
- 创建
Dingledong
类型的临时对象,用 v
初始化
- 创建对象
ding
,用临时对象初始化。
复制构造函数在此处的第 2 步中出现。如果您不想要副本,请不要编写指定副本的代码。例如:
Dingledong ding(v); // no copy
编译器可能会实现一个名为 copy elision 的功能,其中优化了这个临时对象(即使复制构造函数有副作用),但它们没有在这种情况下,没有理由依赖它。
您可以通过添加一个移动构造函数来改进您的代码,在这种情况下(如果编译器不执行复制省略)操作将是移动而不是复制,这样成本更低。
你的构造函数中也有一个浪费的副本(numbers
是从 v
复制的,然后 numbers_
是从 numbers
复制的,还有 numbers_
被初始化然后赋值,而不是仅仅被初始化)。这将是一个更好的构造函数:
Dingledong(std::list<int> numbers): numbers_( std::move(numbers) )
{
std::cout << "construction\n";
}
你的输出:
diller started...
construction //(1)
COPY construction //(2)
destructed //(3)
Has ding been destructed?
destructed //(4)
以及相关的代码位:
Dingledong ding = Dingledong(v);
Dingledong(v) 创建一个临时对象。 (1) 已打印。
您将此临时对象分配给 ding(实际上,这是调用复制构造函数的语法糖)。 (2) 被打印出来。
语句完成后,临时对象将被销毁。 (3) 印刷。
您的代码运行,一旦 ding 超出范围,它就会被销毁,并打印 (4)。
为什么下面的代码会出现"extra"对拷贝构造函数和析构函数?
当叮乐东的构造函数将STL容器作为参数时会发生这种情况(我试过std::vector和std::list)。它可能发生在其他事情上?如果构造函数改为采用指针,则不会发生这种情况。如果我在堆上分配 ding 也不会发生( Dingledong* ding = new Dingledong(v) )。
#include <list>
#include <iostream>
class Dingledong{
public:
Dingledong(std::list<int> numbers)
{
std::cout << "construction\n";
numbers_ = numbers;
}
Dingledong(Dingledong const& other)
{
std::cout << "COPY construction\n";
}
~Dingledong()
{
std::cout << "destructed\n";
// I would do some cleanup here.. unsubscribe from events, whatever..
// but the destructor is called sooner than I would expect and thus
// the cleanup is done prematurely.
}
std::list<int> numbers_;
};
void diller()
{
std::cout << "diller started.. " << std::endl;
std::list<int> v = std::list<int>(34);
// Having an STL container as parameter in constructor causes Dingledong's copy constructor to
// be used to create a temporary Dingledong which is immediately destructed again. Why?
Dingledong ding = Dingledong(v);
std::cout << "Has ding been destructed?\n";
}
int main()
{
diller();
system("pause");
}
输出:
diller started...
construction
COPY construction // I didn't expect this to happen
destructed // I didn't expect this to happen
Has ding been destructed?
destructed
提前致谢!
此代码:
Dingledong ding = Dingledong(v);
表示:
- 创建
Dingledong
类型的临时对象,用v
初始化
- 创建对象
ding
,用临时对象初始化。
复制构造函数在此处的第 2 步中出现。如果您不想要副本,请不要编写指定副本的代码。例如:
Dingledong ding(v); // no copy
编译器可能会实现一个名为 copy elision 的功能,其中优化了这个临时对象(即使复制构造函数有副作用),但它们没有在这种情况下,没有理由依赖它。
您可以通过添加一个移动构造函数来改进您的代码,在这种情况下(如果编译器不执行复制省略)操作将是移动而不是复制,这样成本更低。
你的构造函数中也有一个浪费的副本(numbers
是从 v
复制的,然后 numbers_
是从 numbers
复制的,还有 numbers_
被初始化然后赋值,而不是仅仅被初始化)。这将是一个更好的构造函数:
Dingledong(std::list<int> numbers): numbers_( std::move(numbers) )
{
std::cout << "construction\n";
}
你的输出:
diller started...
construction //(1)
COPY construction //(2)
destructed //(3)
Has ding been destructed?
destructed //(4)
以及相关的代码位:
Dingledong ding = Dingledong(v);
Dingledong(v) 创建一个临时对象。 (1) 已打印。
您将此临时对象分配给 ding(实际上,这是调用复制构造函数的语法糖)。 (2) 被打印出来。
语句完成后,临时对象将被销毁。 (3) 印刷。 您的代码运行,一旦 ding 超出范围,它就会被销毁,并打印 (4)。