4

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

#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 编译它并进行完全优化并运行它,我会得到我所期望的:
Created PG 7837600 -created test
Created PG 7689464 -created ret
Copied PG 7837600 768946 -copied ret to test
Deleted PG 7689464 -deleted ret
结束
已删除 PG 7837600 - 已删除测试

但是,当我在没有优化的情况下在 VS2008 或 VS2012 上编译时,我得到了这个:
Created PG 3888456 -created test
Created PG 4036144 -created ret
Deleted PG 4036144 -deleted ret。等等,我们还没有复制它!
已复制 PG 3888456 4036144 -我们现在正在尝试复制已删除的数据
已删除 PG 4036144 -这已被删除。应用程序崩溃

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

 PG test = CreatePG();

但是我仍然遇到类似的问题,尽管在这种情况下显然使用了复制省略:
Created PG 11228488
Deleted PG 11228488
Ending
Deleted PG 11228488
我仍然得到双重删除。

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

4

3 回答 3

4

这是因为您的代码违反了三规则:由于您没有复制构造函数,因此在您看不到打印输出的场景后面发生了一些重要的事情。

当您没有复制构造函数时,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

于 2013-10-16T15:43:45.053 回答
0

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

让我们逐步检查您的代码

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已经被删除了,所以这是一个很大的问题。

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

于 2013-10-16T16:06:58.247 回答
0

你的复制构造函数在哪里?您需要一个复制构造函数才能返回一个值,而您所拥有的只是默认的复制构造函数,如果使用它,它将导致同一个对象的多次删除(因为您最终会得到多个包含相同对象的对象)指针)。至于为什么优化的时候做一件事,不优化的时候做另一件事,想必NRVO(命名返回值优化)只有在代码优化的时候才会发生。

于 2013-10-16T15:43:09.347 回答