C++ 这是为什么;“三原则”;失败真的失败了吗?

C++ 这是为什么;“三原则”;失败真的失败了吗?,c++,C++,昨天我学到了一个极其宝贵的教训:遵循三个法则 我想我会更容易理解,但错误只出现在delete语句上。情况如下: foo.h class foo{ public: ... foo(int *Y); ~foo(); int *X; } foo.cpp ... (.. constructor sets X to Y..) foo:~foo(){ delete

昨天我学到了一个极其宝贵的教训:遵循三个法则

我想我会更容易理解,但错误只出现在delete语句上。情况如下:

foo.h
    class foo{
    public:
        ...
        foo(int *Y); 
        ~foo();  
        int *X;
    }
 foo.cpp
     ...
     (.. constructor sets X to Y..)
     foo:~foo(){
         delete [] X;
     }

main.cpp
    vector<Foo> fooVec;
    { // this is just to demonstrate scope problems more easily.  
         Y = new int[10000];
         (...init Y...)
         fooVec.push(Foo(Y)) // I get it: this calls copy constructor, violating rule of three
         (...forget to delete Y...)
    } 
    // Y is now out of scope so this is a memory leak
    cout << fooVec[0].[X][0] << " " <<  fooVec[0].[X][1] // THIS WORKS AS INTENDED DUE TO MEMORY LEAK
    // program ends, fooVec goes out of scope
foo.h
福班{
公众:
...
foo(int*Y);
~foo();
int*X;
}
foo.cpp
...
(…构造函数将X设置为Y…)
foo:~foo(){
删除[]X;
}
main.cpp
矢量fooVec;
{//这只是为了更容易地演示范围问题。
Y=新整数[10000];
(…初始Y…)
push(Foo(Y))//我明白了:这调用了复制构造函数,违反了三的规则
(…忘记删除Y…)
} 
//Y现在超出范围,因此这是内存泄漏

cout[注意:
newFoo
不再是原始贴子的一部分,它指的是被推入向量
fooVec
的对象,现在在原处完成:
fooVec.push_back(Foo(Y))
]

执行
双重删除。首先,当
newFoo
超出范围时,它会
delete[]x
(这是第一次删除)。您在此处编写了
忘记删除Y
,但
Y
实际上已被
newFoo
的描述者删除

第二次删除是当
newFoo
的副本被删除时(当
fooVec
超出范围时)。newFoo的副本也会删除[]x
,因为您没有副本构造函数,
x
newFoo
newFoo
的副本中是相同的,因此它是双重删除


现在,您将无法轻松解决此问题。因为在您将要编写的复制构造函数中,您不知道如何复制
x
(它有多少个元素?1?100000?)

我不知道你所说的“这一切都如愿”是什么意思 评论。代码中没有内存泄漏,而是 对同一内存的双重删除。在中范围的末尾 定义后,调用
newFoo
的析构函数;这 依次删除在
Y=new int[10000]
处分配的内存, 并使
fooVec
中的
Foo
对象中的指针无效。 您未定义的行为从那一刻开始,因为
fooVec
现在包含一个形式上不可复制的对象。之后 任何事情都可能发生:未定义的意思就是,未定义

您继续访问内存(未定义的行为),然后 在中对象的析构函数中再次删除它
fooVec
(未定义的行为)

如果没有显式复制构造函数,将复制X中的指针值。以下是发生在你身上的事情:

  • 实例化保存指针值Y的newFoo
  • 将newfoo推到vector上,vector现在保存另一个foo,它也有一个指针值Y
  • newFoo超出范围,析构函数删除值Y指向的内存
  • cout使用指针值Y,但由于已删除的内存尚未被覆盖,因此它“起作用”
  • fooVec超出范围,它所持有的foo的析构函数试图删除Y指向的内存

我只看到一个析构函数。您的复制构造函数和(复制)赋值运算符在哪里?我没有它们,因此“三失败规则”。我的问题是,为什么不让他们杀了我?你会得到默认的复制构造函数和赋值操作符。它将复制指针。现在有两个使用同一指针的对象。第一次删除有效。第二个删除已删除的指针。请将代码缩减为可编译的代码。你会惊讶于这样一个练习是多么的自我帮助。但是,如果newFoo删除了X,为什么
语句不能按预期工作呢?我编辑了我的代码。我从来没有明确创建过newfOO:不确定这是否会改变什么。当你释放内存时,它不会被破坏。你只是说
这里的这段内存现在可以用来做其他事情了
。访问被释放的内存可能会起作用,也可能会对俄罗斯发动核攻击。换句话说,这是未定义的行为@rubenvb在
delete[]x
之后,x不为空。即使在析构函数中将其设置为NULL也无济于事,因为
newFoo
的副本仍将具有原始指针值,并将尝试删除它。@Tommy这样做会更安全,但代价会更高。核心C++哲学是“你只为你所用的东西付费”。你可以创建一个智能指针,当它释放内存时,它会使内存失效。或者是一个保护访问的内存池。或者别的什么。但为什么正确的程序要为捕获坏程序中的错误而付出性能上的代价呢?我现在应该使用vector了。然而,过去我一直避免了解三的规则,因为我总是使用vector,我认为它默认带有一个深度复制构造函数。因此,在这里使用动态分配的数组(大小不是静态的,而是在运行时读取)实际上让我了解了vector为我处理的一些事情。“按预期工作”意味着打印确实在Y中初始化的值。@Tommy这是未定义行为的一个可能结果。当然,这不是唯一的一个,在不同的上下文中,您的代码可能会做不同的事情。newFoo应该更改为Foo(Y)。对此很抱歉,但我从未实际实例化newFoo,我只是在push_-back语句中隐式地进行了实例化。再次抱歉之后更改了我的问题。@Tommy您的编辑并没有真正改变我的答案,只需将
newfoo
替换为
调用pushback()时创建的临时foo
。临时对象超出范围并删除Y指向的内存。还要注意的是,向量在其存储耗尽时将对其所有对象执行额外的复制构造函数/析构函数调用。所以,即使你泄漏了所有的物体