【问题标题】:Passing by value doesn't invoke move constructor按值传递不会调用移动构造函数
【发布时间】:2012-05-05 04:19:07
【问题描述】:

我有以下小班:

/// RAII wrapper for a Lua reference
class reference
{
public:
    /// Construct empty reference
    reference() : m_L(NULL), m_ref(LUA_NOREF) {}

    /// Construct reference from Lua stack
    reference(lua_State* L, int i = -1) : m_L(L) {
        lua_pushvalue(L, i);
        m_ref = luaL_ref(L, LUA_REGISTRYINDEX);
    }

    /// Destructor
    ~reference() {
        if (m_L) luaL_unref(m_L, LUA_REGISTRYINDEX, m_ref);
    }

    /// Copy constructor
    reference(const reference& r) : m_L(r.m_L) {
        r.push();
        m_ref = luaL_ref(m_L, LUA_REGISTRYINDEX);
    }

    /// Move constructor
    reference(reference&& r) : m_L(r.m_L), m_ref(r.m_ref) {
        r.m_L = NULL; // make sure r's destructor is NOP
    }

    /// Assignment operator
    reference& operator=(reference r) {
        swap(r, *this);
        return *this;
    }

    /// Swap with other reference
    friend void swap(reference& a, reference& b)
    {
        std::swap(a.m_L,   b.m_L);
        std::swap(a.m_ref, b.m_ref);
    }

    void push() const { lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_ref); }

private:        
    lua_State* m_L;
    int        m_ref;
};

请注意,赋值运算符是使用copy-and-swap idiom 实现的,如果与右值一起使用,则使用supposed 来调用移动构造函数。

但是,reference r; r = reference(L); 在进入赋值运算符之前调用了复制构造函数。为什么,哦,为什么?

two assignment operators 有帮助:

    /// Assignment operator
    reference& operator=(const reference& r) {
        reference copy(r);
        swap(copy, *this);
        return *this;
    }

    /// Move assignment operator
    reference& operator=(reference&& r) {
        swap(r, *this);
        return *this;
    }

但是,以禁用复制省略为代价。

按值传递不应该按预期在这里工作吗?还是我的编译器(Mac 上的 Clang)坏了?

更新:

以下小测试用例正常工作:

#include <iostream>

using namespace std;

struct resource
{
    resource(int i=1) : i(i) { print(); }
    ~resource() { print(); i = 0; }

    void print() const
    {
        cout << hex << " " << uint16_t(uintptr_t(this)) << ") " << dec;
    }

    int i;
};

resource* alloc_res()
{
    cout << " (alloc_res";
    return new resource(0);
}

resource* copy_res(resource* r)
{
    cout << " (copy_res";
    return new resource(r->i);
}

void free_res(resource* r)
{
    if (r) cout << " (free_res";
    delete r;
}

struct Test
{
    void print() const
    {
        cout << hex << " [&="   << uint16_t(uintptr_t(this))
             << ", r=" << uint16_t(uintptr_t(r)) << "] " << dec;
    }

    explicit Test(int j = 0) : r(j ? alloc_res() : NULL) {
        cout << "constructor"; print();
        cout << endl;
    }

    Test(Test&& t) : r(t.r) {
        cout << "move"; print(); cout << "from"; t.print();
        t.r = nullptr;
        cout << endl;
    }

    Test(const Test& t) : r(t.r ? copy_res(t.r) : nullptr) {
        cout << "copy"; print(); cout << "from"; t.print();
        cout << endl;
    }

    Test& operator=(Test t) {
        cout << "assignment"; print(); cout << "from"; t.print(); cout << "  ";
        swap(t);
        return *this;
        cout << endl;
    }

    void swap(Test& t)
    {
        cout << "swapping"; print();
        cout << "and"; t.print();
        std::swap(r, t.r);
        cout << endl;
    }

    ~Test()
    {
        cout << "destructor"; print();
        free_res(r);
        cout << endl;
    }

    resource* r;
};

int main()
{
    Test t;
    t = Test(5);
}

如果使用clang++ --std=c++11 -O0 -fno-elide-constructors test.cpp -o test 编译,则调用移动构造函数。 (感谢本杰明·林德利的转换)

现在的问题是:为什么它现在起作用了?有什么区别?

【问题讨论】:

  • r = reference(L) 中的L 是什么?那是一个 lua_state 指针吗?
  • 您是否在启用优化的情况下进行编译?
  • 我用 g++ 4.6.3 试过这段代码,没有调用复制构造函数。
  • 然后尝试优化。编译器被允许(不是必需的)删除副本,所以它可能只在优化时这样做。
  • 问题似乎是关于为什么没有发生移动。在适用时需要移动,无论是否优化。

标签: c++ lua c++11 assignment-operator rvalue-reference


【解决方案1】:

没有合法的 C++11 情况会导致在 r = reference(L); 中调用复制构造函数。

这实际上等同于r.operator =(reference(L));。由于operator= 按值获取参数,因此会发生以下两种情况之一。

  1. 临时值将用于构造值。由于它是临时的,它会优先调用reference的移动构造函数,从而导致移动。
  2. 临时值将直接省略到值参数中。不得复制或移动。

之后,operator= 将被调用,它不会在内部进行任何复制。

所以这看起来像是一个编译器错误。

【讨论】:

  • 谢谢。另一个迹象是原始问题中更新的示例代码有效。但是,我没有找到相应的bug llvm的Bugzilla。
猜你喜欢
  • 2011-05-22
  • 2015-02-21
  • 2012-10-27
  • 1970-01-01
  • 2018-01-20
  • 1970-01-01
  • 1970-01-01
  • 2017-11-03
  • 2022-11-21
相关资源
最近更新 更多