返回C++对象时的复制和销毁
Copying and destruction when returning a C++ object
我有一段相当简单的测试代码:
#include <stdio.h>
class PG
{
public:
PG(){
m_ptr = new int;
printf("Created PG %in", (int)m_ptr);
}
~PG(){
printf("Deleted PG %in", (int)m_ptr);
delete (m_ptr);
}
PG& operator =(const PG& src)
{
printf("Copied PG %i %in", (int)m_ptr, (int)src.m_ptr);
return(*this);
}
private:
int * m_ptr;
};
PG CreatePG()
{
PG ret;
return ret;
}
int main(int argc, char* argv[])
{
PG test;
test = CreatePG();
printf("Endingn");
return 0;
}
如果我使用 GCC、VS2008 或 VS2012 进行全面优化并运行它,我会得到我所期望的:
创建 PG 7837600 - 创建的测试
创建 PG 7689464 创建 ret
复制 PG 7837600 768946 - 复制的 ret 进行测试
已删除的PG 7689464 - 已删除的ret
结束
已删除 PG 7837600 - 已删除
但是,当我在没有优化的情况下在VS2008或VS2012上编译时,我得到这个:
创建的 PG 3888456 创建的测试
创建 PG 4036144 创建 ret
删除了PG 4036144 - 删除了。 等等,我们还没有复制它!
复制的PG 3888456 4036144 - 我们现在正在尝试复制已删除的数据
已删除的PG 4036144 - 这已被删除。应用程序崩溃
我不敢相信这是VS中从未修复的错误,但我也看不出我做错了什么。在我的应用程序中,我有一个实例,在编译针对速度优化的更复杂的类时,也会发生这种行为。我知道使用会更有效:
PG test = CreatePG();
但我仍然遇到类似的问题,
尽管在这种情况下它显然使用了复制省略:创建 PG 11228488
删除的PG 11228488
结束
删除的PG 11228488
我仍然得到双重删除。
如果有人能对此有所了解,我将不胜感激。
这是因为您的代码违反了三法则:由于您没有复制构造函数,因此幕后发生了一些重要的事情,您看不到打印输出。
当您没有复制构造函数时,C++很乐意为您定义一个。这通常是您想要的确切构造函数,但一种情况除外:当您的类显式管理资源时。在这种情况下,逐字节复制内容会创建一个假别名,当同一个指针可以多次删除时。启用优化时,编译器会跳过复制构造函数的调用(返回值优化)。但是,关闭优化后,将调用复制构造函数,然后删除m_ptr
的副本,从而保留指向已删除内存的实际指针。
以下是解决此问题的方法:
PG& operator =(const PG& src) {
*m_ptr = *(other->m_ptr);
printf("Assigned PG %x %xn", (void*)m_ptr, (void*)src.m_ptr);
return(*this);
}
PG(const PG& other) {
m_ptr = new int;
*m_ptr = *(other->m_ptr);
printf("Copied PG %xn", (void*)m_ptr);
}
注: 未定义将指针转换为int
;应将指针转换为void*
,并使用%x
格式说明符进行打印。
你的复制构造函数在哪里? 您需要一个复制构造函数命令返回一个值,你得到的只是默认副本构造函数,这将导致多次删除相同的对象(如果使用)(因为您最终会得到多个包含相同指针的对象)。 至于为什么它做一个优化时的东西,不优化时的另一个,大概是 NRVO(称为返回值优化)仅在代码已优化。
这就是您遇到问题的原因。
让我们逐步完成您的代码
PG test;
调用 PG::P G(),这会创建一个带有 ptr 的新 PG,以在地址 X 处的堆上分配的新 int,打印"创建的 PG X"
test = CreatePG();
首次调用 CreatePG()
PG ret;
调用 PG::P G(),这会创建一个带有 ptr 的新 PG,以在地址 Y 处的堆上分配的新 int,打印"创建的 PG Y"
return ret;
由于该函数声明为按值返回,因此使用执行按位复制的默认复制构造函数。 所以未命名副本的m_ptr返回的是 Y。
当我们离开 CreatePG() 的范围时,调用本地对象的析构函数,它打印"删除了 PG Y",然后删除了 Y。 然后我们回到 main() 中的赋值。
test = CreatePG();
我们现在使用 operator= 分配临时未命名的 PG 进行测试。 这将打印"复制的 PG X Y",然后简单地返回对测试的引用,而无需实际执行任何操作。
在表达式结束时,临时超出范围并调用析构函数。 析构函数打印"已删除的 PG Y"并尝试删除 Y,但 Y 已被删除,因此这是一个很大的问题。
编写处理指向堆的指针的复制构造函数的建议很好,可以帮助您解决问题。
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 在C++程序中输入的文本文件将不起作用,除非文本被复制和粘贴
- 使用strcpy将char数组的元素复制到另一个数组
- 是否可以初始化不可复制类型的成员变量(或基类)
- 为什么在C++中使用私有复制构造函数与删除复制构造函数
- C++ Windows 驱动程序MSB3030无法复制该文件,因为它找不到
- 复制列表初始化的隐式转换的等级是多少
- 当从函数参数中的临时值调用复制构造函数时
- 有可能在Armadillo中复制MATLAB circshift方法吗
- 复制几乎为空的数组的最快方法
- 以下示例中如何避免代码复制?C++/库达
- 如果有一个模板构造函数只有一个泛型参数,为什么我必须有一个复制构造函数
- 为什么需要复制构造函数,在哪些情况下它们非常有用
- 不能将复制初始化与隐式转换的多个步骤一起使用
- 当有分配器意识的容器被复制/移动时,反弹分配器是否被复制/移走
- 为什么复制而不是移动数据元素?
- 文件系统:复制功能的速度秘诀是什么
- 使用仅使用一次的变量调用的复制构造函数.这可能是通过调用move构造函数进行编译器优化的情况吗
- 为什么类中的ostringstream类型的成员会导致";调用隐含删除复制构造函数";错误
- 使lambda不可复制/不可移动