我在带有最新更新的 Visual Studio 2022 社区版中使用 Visual C++。在下面的 C++ 代码中,main 创建了一个 ShowBug 实例并将其分配给变量 sb。下一行创建 ShowBug 的第二个实例并将其分配给 sb.
在第二行完成之前,它调用了析构函数。但是,它并没有破坏第一个实例,这是我认为它会做的,而是破坏了新创建的第二个实例。如果您觉得难以置信,请亲自尝试一下。
所以,我在这里遗漏了什么(即使用相同的变量来分配一个新实例是一种糟糕的编程习惯吗?),或者编译器是否以某种方式做了正确的事情?或者,这是一个编译器错误吗?
// ShowBug.h:
using namespace std;
#include <iostream>
#include <string>
class ShowBug
{
// This class is used to show that the destructor seems to be called for the wrong instance.
//
string S;
int *pArray;
public:
inline ShowBug(const string &s) : S(s)
{
}
inline ~ShowBug()
{
std::cout << "Deleting " + S;
}
};
int main()
{
ShowBug sb = ShowBug("First Instance");
sb = ShowBug("Second Instance");
}
当你做
sb = ShowBug("Second Instance");
表达式
ShowBug("Second Instance")
创建一个 ShowBug
类型的 temporary对象。一旦完整的表达式(赋值)完成,这个临时对象将立即被销毁。
这当然会导致问题,因为它的销毁也会释放分配的内存。指针将通过赋值进行复制,但仅指针本身而不是它指向的内存。
所以在赋值之后指针
sb.pArray
将不再有效。除了使用错误的 delete
运算符外,再次尝试 delete
无效指针会导致 undefined behavior.
解决方案是遵循三、五或零的规则之一。我推荐零规则,通过使用
std::vector<int>
或者在你的情况下 std::array<int, 3>
而不是你现在做的动态分配。然后你可以删除析构函数。
如果你必须使用指针和动态分配,那么你至少需要遵循三的规则,并实现一个复制构造函数和一个复制赋值运算符。
我也忽略了初始化
ShowBug sb = ShowBug("First Instance");
有完全相同的问题:您创建了一个临时对象,用于复制构造
sb
对象。然后临时对象被破坏,带走内存和原始指针。
解决方案仍然相同:零、三或五的规则。