【问题标题】:Segmentation fault when specifying rvalue as return value将右值指定为返回值时出现分段错误
【发布时间】:2015-04-16 05:56:45
【问题描述】:

我有一个构造新对象的工厂类。 新对象不应该被复制,但可能被移动。所以我想我会在提供移动的同时删除复制构造函数和复制赋值运算符构造函数和移动赋值运算符。

我认为,为了帮助传达对象必须移动到位而不是复制的想法,我将返回一个右值引用。但是,在执行此操作时,编译器似乎总是生成代码来销毁返回的“过期”对象,然后将相同(已销毁!)对象提供给移动构造函数或移动赋值运算符。

所以我想知道;我违反了标准中的某些内容吗? 如果是,是否有警告(错误,最好是)我可以启用以防止我继续做如此愚蠢的事情?或者,如果我没有违反任何标准,那么我认为这是一个编译器错误?

// 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*)&copy_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*)&copy_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.2Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn) 尝试过这个。

【问题讨论】:

  • make_ptr_r 返回对局部变量的引用。函数退出时变量消失,引用变得悬空。任何实际使用此引用的尝试都会表现出未定义的行为。这是一个右值引用这一事实不会改变任何事情。
  • 补充@IgorTandetnik所说的:make_ptr_r的返回类型应该是PTR,没有&amp;&amp;。检查你的代码输出,看看它是否对你有意义。
  • @DanielFrey 我确实检查了输出,甚至提到它没有多大意义。我误解的是返回类型为右值引用的 std::move() 应该将对象移出本地的想法。
  • @inetknght 抱歉,无意冒犯!我的意思是输出 after 您按照我的建议更改返回类型并将其与您之前的输出进行比较。我希望差异能帮助您理解,即使在返回类型上没有 &amp;&amp;,您的 move-ctor 和 move-assignment 也将是正确的,并有助于减少复制操作。
  • std::move 不移动任何东西。这只是将对象转换为右值引用的一种便捷方式:std::move(x) 大致相当于static_cast&lt;decltype(x)&amp;&amp;&gt;(x)。其他东西(例如移动构造函数)现在可以将此引用作为参数,并将数据移出x

标签: c++ c++11 gcc clang rvalue-reference


【解决方案1】:

您正在返回对临时对象的引用。您可以从输出中看出这一点:

rvalue new: 
    ctor  @0x7ffed71b7a00
    dtor~~@0x7ffed71b7a00
    ctor&&@0x7ffed71b7a40   <@0x7ffed71b7a00

你构造和销毁7a00 之前你用它移动构造7a40。您碰巧返回右值引用而不是左值引用这一事实并不重要。它基本上仍然是某种形式:

T& foo() {
    T object;
    return object;
}

这就是make_ptr_l 起作用的原因——你返回的是一个值,而不是一个引用。而std::move() 则没有必要。

【讨论】:

  • 所以在这种情况下,右值引用不会像我理解的那样移动对象?
  • @inetknght std::move() 不会移动。这只是一个演员表。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-22
  • 1970-01-01
  • 1970-01-01
  • 2020-02-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多