将右值指定为 return 值时出现分段错误
Segmentation fault when specifying rvalue as return value
我有一个构造新对象的工厂class。 新对象不应该被复制但是可以移动。所以我想我会删除副本构造函数和复制赋值运算符,同时提供移动构造函数和移动赋值运算符。
我认为,为了帮助传达对象必须 移动 到位而不是复制的想法,我会 return 一个右值引用。然而,在这样做时,似乎编译器总是生成代码来破坏正在 returned 的 "expiring" 对象,然后将相同的(被破坏的!)对象提供给移动构造函数或移动赋值运算符。
所以我想知道;我违反了标准中的某些内容吗? 如果是这样,是否可以启用警告(最好是错误)以防止我继续做如此愚蠢的事情?否则,如果我没有违反任何标准,那么我认为这是一个编译器错误?
// g++ (Ubuntu 4.9.2-0ubuntu1~14.04) 4.9.2
// g++ test.cpp -std=c++11
#include <iostream>
#include <memory>
struct PTR {
char * blah = nullptr;
PTR(char* blah) : blah(blah)
{
std::cout << "\tctor @" << (void*)this << std::endl;
}
PTR(const PTR & copy_ctor) : blah(new char)
{
*blah = *copy_ctor.blah;
std::cout << "\tcopy @@" << (void*)this << "\t<-" << (void*)©_ctor << std::endl;
}
PTR(PTR && move_ctor) : blah(move_ctor.blah)
{
move_ctor.blah = nullptr;
std::cout << "\tctor&&@" << (void*)this << "\t<@" << (void*)&move_ctor << std::endl;
}
PTR & operator=(const PTR & copy_assign)
{ delete blah;
blah = new char;
*blah = *copy_assign.blah;
std::cout << "copyas@" << (void*)this << "\t<-" << (void*)©_assign << std::endl;
return *this;
}
PTR & operator=(PTR && move_assign)
{
delete blah;
blah = move_assign.blah;
move_assign.blah = nullptr;
std::cout << "\tmove&&@" << (void*)this << "\t<@" << (void*)&move_assign << std::endl;
return *this;
}
~PTR()
{
std::cout << "\tdtor~~@" << (void*)this << std::endl;
delete blah;
}
};
PTR make_ptr_l() {
PTR ptr(new char());
return std::move(ptr); // Without std::move, compiler *may* opt to copy the class, which is undesired
}
PTR && make_ptr_r() {
PTR ptr(new char());
return std::move(ptr); // Requires std::move to turn ptr into rvalue, otherwise compiler error
}
int main() {
std::cout << "lvalue: \n" << std::flush;
PTR ptr = make_ptr_l();
std::cout << "successful\nrvalue new: \n" << std::flush;
{
PTR ptr_r = make_ptr_r();
std::cout << "successful\nrvalue assign: \n" << std::flush;
}
ptr = make_ptr_r();
std::cout << "successful" << std::endl;
return 0;
}
使用上面的代码,可以看到如下输出:
lvalue:
ctor @0x7ffed71b7a00
ctor&&@0x7ffed71b7a30 <@0x7ffed71b7a00
dtor~~@0x7ffed71b7a00
successful
rvalue new:
ctor @0x7ffed71b7a00
dtor~~@0x7ffed71b7a00
ctor&&@0x7ffed71b7a40 <@0x7ffed71b7a00
successful
rvalue assign:
dtor~~@0x7ffed71b7a40
*** Error in `./a.out': double free or corruption (fasttop): 0x0000000001d1bc40 ***
Aborted (core dumped)
正如你在rvalue new
之后看到的那样,构造了一个对象,然后立即销毁,然后将销毁的对象传递给移动构造函数。由于移动构造函数因此访问了被销毁的变量,这就是分段错误的根源。
我已经用 g++ (Ubuntu 4.9.2-0ubuntu1~14.04) 4.9.2
和 Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn)
试过了。
您正在 return引用临时文件。您可以从输出中看出这一点:
rvalue new:
ctor @0x7ffed71b7a00
dtor~~@0x7ffed71b7a00
ctor&&@0x7ffed71b7a40 <@0x7ffed71b7a00
你在7a00
之前建造并销毁你用它移动构造7a40
。您碰巧 return 右值引用而不是左值引用这一事实并不重要。它基本上仍然是以下形式:
T& foo() {
T object;
return object;
}
这就是 make_ptr_l
起作用的原因 - 您 return 是一个值,而不是一个引用。而std::move()
就没有必要了。
我有一个构造新对象的工厂class。 新对象不应该被复制但是可以移动。所以我想我会删除副本构造函数和复制赋值运算符,同时提供移动构造函数和移动赋值运算符。
我认为,为了帮助传达对象必须 移动 到位而不是复制的想法,我会 return 一个右值引用。然而,在这样做时,似乎编译器总是生成代码来破坏正在 returned 的 "expiring" 对象,然后将相同的(被破坏的!)对象提供给移动构造函数或移动赋值运算符。
所以我想知道;我违反了标准中的某些内容吗? 如果是这样,是否可以启用警告(最好是错误)以防止我继续做如此愚蠢的事情?否则,如果我没有违反任何标准,那么我认为这是一个编译器错误?
// g++ (Ubuntu 4.9.2-0ubuntu1~14.04) 4.9.2
// g++ test.cpp -std=c++11
#include <iostream>
#include <memory>
struct PTR {
char * blah = nullptr;
PTR(char* blah) : blah(blah)
{
std::cout << "\tctor @" << (void*)this << std::endl;
}
PTR(const PTR & copy_ctor) : blah(new char)
{
*blah = *copy_ctor.blah;
std::cout << "\tcopy @@" << (void*)this << "\t<-" << (void*)©_ctor << std::endl;
}
PTR(PTR && move_ctor) : blah(move_ctor.blah)
{
move_ctor.blah = nullptr;
std::cout << "\tctor&&@" << (void*)this << "\t<@" << (void*)&move_ctor << std::endl;
}
PTR & operator=(const PTR & copy_assign)
{ delete blah;
blah = new char;
*blah = *copy_assign.blah;
std::cout << "copyas@" << (void*)this << "\t<-" << (void*)©_assign << std::endl;
return *this;
}
PTR & operator=(PTR && move_assign)
{
delete blah;
blah = move_assign.blah;
move_assign.blah = nullptr;
std::cout << "\tmove&&@" << (void*)this << "\t<@" << (void*)&move_assign << std::endl;
return *this;
}
~PTR()
{
std::cout << "\tdtor~~@" << (void*)this << std::endl;
delete blah;
}
};
PTR make_ptr_l() {
PTR ptr(new char());
return std::move(ptr); // Without std::move, compiler *may* opt to copy the class, which is undesired
}
PTR && make_ptr_r() {
PTR ptr(new char());
return std::move(ptr); // Requires std::move to turn ptr into rvalue, otherwise compiler error
}
int main() {
std::cout << "lvalue: \n" << std::flush;
PTR ptr = make_ptr_l();
std::cout << "successful\nrvalue new: \n" << std::flush;
{
PTR ptr_r = make_ptr_r();
std::cout << "successful\nrvalue assign: \n" << std::flush;
}
ptr = make_ptr_r();
std::cout << "successful" << std::endl;
return 0;
}
使用上面的代码,可以看到如下输出:
lvalue:
ctor @0x7ffed71b7a00
ctor&&@0x7ffed71b7a30 <@0x7ffed71b7a00
dtor~~@0x7ffed71b7a00
successful
rvalue new:
ctor @0x7ffed71b7a00
dtor~~@0x7ffed71b7a00
ctor&&@0x7ffed71b7a40 <@0x7ffed71b7a00
successful
rvalue assign:
dtor~~@0x7ffed71b7a40
*** Error in `./a.out': double free or corruption (fasttop): 0x0000000001d1bc40 ***
Aborted (core dumped)
正如你在rvalue new
之后看到的那样,构造了一个对象,然后立即销毁,然后将销毁的对象传递给移动构造函数。由于移动构造函数因此访问了被销毁的变量,这就是分段错误的根源。
我已经用 g++ (Ubuntu 4.9.2-0ubuntu1~14.04) 4.9.2
和 Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn)
试过了。
您正在 return引用临时文件。您可以从输出中看出这一点:
rvalue new:
ctor @0x7ffed71b7a00
dtor~~@0x7ffed71b7a00
ctor&&@0x7ffed71b7a40 <@0x7ffed71b7a00
你在7a00
之前建造并销毁你用它移动构造7a40
。您碰巧 return 右值引用而不是左值引用这一事实并不重要。它基本上仍然是以下形式:
T& foo() {
T object;
return object;
}
这就是 make_ptr_l
起作用的原因 - 您 return 是一个值,而不是一个引用。而std::move()
就没有必要了。