【问题标题】:Copying and destruction when returning a C++ object返回 C++ 对象时的复制和销毁
【发布时间】:2013-10-24 19:40:06
【问题描述】:

我有一段相当简单的测试代码:

#include <stdio.h>

class PG
{
public:

PG(){
    m_ptr = new int;
    printf("Created PG %i\n", (int)m_ptr);
} 

~PG(){
    printf("Deleted PG %i\n", (int)m_ptr);
    delete (m_ptr);
}

PG& operator =(const PG& src)
{
    printf("Copied PG %i %i\n", (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("Ending\n");
    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 -已删除 ret。等等,我们还没有复制它!
已复制 PG 3888456 4036144 -我们现在正在尝试复制已删除的数据
已删除 PG 4036144 -这已被删除。应用程序崩溃

我不敢相信这是 VS 中从未修复过的错误,但我也看不出我做错了什么。在我的应用程序中,我有一个实例,在编译针对速度优化的更复杂的类时也会发生这种行为。我知道使用起来会更有效率:

 PG test = CreatePG();

但我仍然遇到类似的问题,尽管在这种情况下显然使用了复制省略:
创建 PG 11228488
已删除 PG 11228488
结局
已删除 PG 11228488
我仍然收到双重删除。

如果有人能对此有所了解,我将不胜感激。

【问题讨论】:

  • 您需要提供带有检测功能的复制构造函数。
  • 你的operator = 没有做任何事情。
  • @PeteBecker 他需要提供一个拷贝构造函数,句号。否则,他会被双重删除。
  • @JamesKanze - 没有检测的复制构造函数将无法提供准确的跟踪。
  • 谢谢。这正是我一直在寻找的。我非常关注优化和非优化之间的不同行为,以至于我完全忽略了这一点。

标签: c++ visual-studio object visual-studio-2012 return


【解决方案1】:

这是因为您的代码违反了rule of three:因为您没有复制构造函数,所以在您看不到打印输出的幕后发生了一些重要的事情。

当您没有复制构造函数时,C++ 很乐意为您定义一个。这通常是您想要的确切构造函数,除了在一种情况下:当您的类显式管理资源时。在这种情况下,当同一个指针可以被多次删除时,逐字节复制内容会创建一个假别名。当您打开优化时,编译器会跳过复制构造函数的调用(返回值优化)。然而,在优化关闭的情况下,复制构造函数被调用,然后m_ptr 的副本被删除,留下指向已删除内存的实际指针。

以下是解决此问题的方法:

PG& operator =(const PG& src) {
    *m_ptr = *(other->m_ptr);
    printf("Assigned PG %x %x\n", (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 %x\n", (void*)m_ptr);
}

注意:将指针转换为int 没有定义;您应该将指针转换为void*,并使用%x 格式说明符进行打印。

【讨论】:

  • 你也可以定义一个私有的空拷贝构造函数,它禁止编译器隐式定义。这样,当使用私有(不可访问)复制构造函数时会发生错误。这可能有助于调试。
【解决方案2】:

你的复制构造函数在哪里?你需要一个复制构造函数 为了返回一个值,你所拥有的只是默认副本 构造函数,这将导致多次删除相同的 对象,如果它被使用(因为你最终会得到多个 包含相同指针的对象)。至于为什么这样做 优化时的事情,而不是优化时的事情,大概是 NRVO (称为返回值优化)仅在 代码进行了优化。

【讨论】:

    【解决方案3】:

    这就是您遇到问题的原因。

    让我们逐步检查您的代码

    PG test;  
    

    调用 PG::PG() 创建一个新的 PG,其 ptr 指向在地址 X 的堆上分配的新 int,打印“Created PG X”

    test = CreatePG();
    

    首先调用 CreatePG()

    PG ret;
    

    调用 PG::PG() 创建一个新的 PG,其 ptr 指向在地址 Y 的堆上分配的新 int,打印“Created PG Y”

    return ret;
    

    由于函数被声明为按值返回,因此使用返回 ret 的副本 执行按位复制的默认复制构造函数。所以未命名副本的 m_ptr 返回的是 Y。

    当我们离开 CreatePG() 的作用域时,本地对象的析构函数被调用,它打印 “Deleted PG Y”然后删除Y。然后我们回到main()中的赋值。

    test = CreatePG();
    

    我们现在使用 operator= 分配临时未命名的 PG 进行测试。这将打印“Copied PG X Y”,然后简单地返回一个引用以进行测试,而无需实际执行任何操作。

    在表达式结束时,临时变量超出范围并调用析构函数。析构函数打印“Deleted PG Y”并试图删除Y,但是Y已经被删除了,所以这是一个很大的问题。

    编写处理指向堆指针的复制构造函数的建议很好,可以帮助您解决问题。

    【讨论】:

      猜你喜欢
      • 2020-08-02
      • 2019-05-14
      • 2016-02-15
      • 2015-12-11
      • 1970-01-01
      • 1970-01-01
      • 2014-09-24
      • 1970-01-01
      • 2023-02-08
      相关资源
      最近更新 更多