我从 this question 中获取了代码并通过显式调用 move 构造对象之一的析构函数来编辑它以产生段错误:
using namespace std;
struct Foo
{
Foo()
{
s = new char[100];
cout << "Constructor called!" << endl;
}
Foo(const Foo& f) = delete;
Foo(Foo&& f) :
s{f.s}
{
cout << "Move ctor called!" << endl;
f.s = nullptr;
}
~Foo()
{
cout << "Destructor called!" << endl;
cout << "s null? " << (s == nullptr) << endl;
delete[] s; // okay if s is NULL
}
char* s;
};
void work(Foo&& f2)
{
cout << "About to create f3..." << endl;
Foo f3(move(f2));
// f3.~Foo();
}
int main()
{
Foo f1;
work(move(f1));
}
编译并运行此代码(使用 G++ 4.9)产生以下输出:
Constructor called!
About to create f3...
Move ctor called!
Destructor called!
s null? 0
Destructor called!
s null? 0
*** glibc detected *** ./a.out: double free or corruption (!prev): 0x0916a060 ***
请注意,当析构函数未被显式调用时,不会发生 double-free 错误。
现在,当我更改 s
的类型时至 unique_ptr<char[]>
并删除 delete[] s
在 ~Foo()
和 f.s = nullptr
在 Foo(Foo&&)
(请参阅下面的完整代码),我没有得到双重释放错误:
Constructor called!
About to create f3...
Move ctor called!
Destructor called!
s null? 0
Destructor called!
s null? 1
Destructor called!
s null? 1
这是怎么回事?为什么 move 对象的数据成员是unique_ptr
时可以显式删除? ,但在 Foo(Foo&&)
中手动处理移出对象的失效时则不然?由于 move 构造函数被调用时 f3
已创建(如“Move ctor called!”行所示),为什么第一个析构函数调用(可能是 f3
)声明 s
不 是空的吗?如果答案只是f3
和 f2
由于优化,不知何故实际上是同一个对象,什么是 unique_ptr
这样做可以防止该实现发生同样的问题吗?
编辑: 根据要求,完整代码使用 unique_ptr
:
using namespace std;
struct Foo
{
Foo() :
s{new char[100]}
{
cout << "Constructor called!" << endl;
}
Foo(const Foo& f) = delete;
Foo(Foo&& f) :
s{move(f.s)}
{
cout << "Move ctor called!" << endl;
}
~Foo()
{
cout << "Destructor called!" << endl;
cout << "s null? " << (s == nullptr) << endl;
}
unique_ptr<char[]> s;
};
void work(Foo&& f2)
{
cout << "About to create f3..." << endl;
Foo f3(move(f2));
f3.~Foo();
}
int main()
{
Foo f1;
work(move(f1));
}
我已经仔细检查过这会产生上面复制的输出。
EDIT2:实际上,使用 Coliru(参见下面 T.C. 的链接),这段代码确实产生了双重删除错误。
最佳答案
对于任何具有非平凡析构函数的类,根据核心语言规则,两次销毁它是未定义的行为:
[基本生活]/p1:
The lifetime of an object of type
T
ends when:
- if
T
is a class type with a non-trivial destructor (12.4), the destructor call starts, or- the storage which the object occupies is reused or released.
[类.dtor]/p15:
the behavior is undefined if the destructor is invoked for an object whose lifetime has ended (3.8)
您的代码两次销毁 f3
,一次是通过显式析构函数调用,一次是通过离开作用域,因此它具有未定义的行为。
碰巧 libstdc++ 和 libc++ 的 unique_ptr
析构函数都会将空指针分配给存储的指针(libc++ 调用 reset()
;libstdc++ 手动执行)。这不是标准所要求的,并且可以说是一个性能错误,它意味着对原始指针的零开销包装。因此,您的代码在 -O0
中“有效”。
g++ 在 -O2
能够看到析构函数中的赋值不可能被定义良好的程序观察到,因此它优化了赋值,导致双重删除。
关于c++ - 为什么这个 RAII 只 move 类型不能正确模拟 `std::unique_ptr` ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30466949/