【问题标题】:User-defined move constructor for member of lvalue reference type左值引用类型成员的用户定义移动构造函数
【发布时间】:2015-03-07 05:23:44
【问题描述】:

我正在使用编译器上的移动语义,它具有右值引用但不支持默认的移动构造函数。我想生成类似于下面的包装类的东西,即使模板参数是左值引用,它也可以工作。但是,这种简单的方法无法编译,因为它尝试从 int 初始化 int&。

#define USER_DEFINED   0

template <typename T>
struct Wrapper
{
    Wrapper(T t)
        : m_t(t)
    {
    }

    Wrapper(const Wrapper&) = delete;
    Wrapper& operator=(const Wrapper&) = delete;

#if USER_DEFINED
    Wrapper(Wrapper&& w)
        : m_t(std::move(w.m_t))
    {
    }
#else
    Wrapper(Wrapper&&) = default;
#endif

private:
    T m_t;
};

int main()
{
    int i = 0;
    Wrapper<int&> w1 = Wrapper<int&>(i);
    Wrapper<std::string> w2 = Wrapper<std::string>("text");
}

显而易见的解决方案是使用两个移动构造函数,一个用于左值引用,一个用于所有其他类型。例如这样的事情:

template <typename U = T>
Wrapper(typename std::enable_if<!std::is_lvalue_reference<U>::value, Wrapper>::type&& w)
    : m_t(std::move(w.m_t))
{
}

template <typename U = T>
Wrapper(typename std::enable_if<std::is_lvalue_reference<U>::value, Wrapper>::type&& w)
    : m_t(w.m_t)
{
}

所以这是要走的路吗?也许enable_if&lt;&gt; 中的表达式应该更通用?或者我可以使用与 std::move() 不同的东西并为所有类型使用一个构造函数吗?

【问题讨论】:

  • 我很犹豫是否将其发布为答案,因为我不确定它是否会做正确的事情,但是将 std::move 替换为 std::forward 怎么样?
  • @5gon12eder:去吧。你在正确的轨道上。
  • @HowardHinnant 谢谢。我试了一下,它似乎有效,但如果您有时间,我会很高兴收到一些反馈,因为我不完全理解它为什么真的有效。

标签: c++ c++11 move-semantics


【解决方案1】:

好的,这是一个我认为可以按照您的意愿工作的解决方案,但我必须承认我并不完全理解它是如何做到的。

我所做的最重要的更改是在移动构造函数中将std::move 替换为std::forward&lt;T&gt;。我还添加了一个移动赋值运算符,但这是我不明白的事情:除非在其他地方,它需要std::move 而不是std::forward!最后,我还在您的构造函数中添加了一个std::forward,它接受T,因此它不会复制其参数的两个。实际上要求我们按值接受T,并在此处使用std::forward。如果 T 是引用,const T&amp;T&amp;&amp; 的重载将失败,因为 T&amp;&amp; 也将 collapse 指向左值引用,并且重载变得不明确。

#include <iostream>
#include <utility>

template <typename T>
class Wrapper
{

public:

  Wrapper(T t) : m_t {std::forward<T>(t)}
  {
  }

  Wrapper(Wrapper&& w) : m_t {std::forward<T>(w.m_t)}
  {
  }

  Wrapper(const Wrapper&) = delete;

  Wrapper&
  operator=(Wrapper&& w)
  {
    if (this != &w)
      this->m_t = std::move(w.m_t);
    return *this;
  }

  Wrapper&
  operator=(const Wrapper&) = delete;

private:

  T m_t;
};

现在,让我们用一种可以让我们看到发生了什么的仪表类型进行试驾。

struct A
{
  A ()
  {
    std::cerr << "A was default-constructed" << std::endl;
  }

  A (const A&)
  {
    std::cerr << "A was copy-constructed" << std::endl;
  }

  A (A&&)
  {
    std::cerr << "A was move-constructed" << std::endl;
  }

  A&
  operator=(const A&)
  {
    std::cerr << "A was copy-assigned" << std::endl;
    return *this;
  }

  A&
  operator=(A&&)
  {
    std::cerr << "A was move-assigned" << std::endl;
    return *this;
  }

  ~A ()
  {
    std::cerr << "A was destroyed" << std::endl;
  }
};

int main()
{
  A a {};
  Wrapper<A> val1 {a};
  Wrapper<A> val2 {std::move(val1)};
  val1 = std::move(val2);
  Wrapper<A&> ref1 {a};
  Wrapper<A&> ref2 {std::move(ref1)};
  ref1 = std::move(ref2);
}

使用 GCC 4.9.1 编译(整个练习实际上相当没有意义,因为它支持已经开箱即用的各种移动。)并且关闭所有优化,输出如下(添加 cmets)。

A was default-constructed  ; automatic variable a in main
A was copy-constructed     ; copied as Wrapper<A>'s constructor argument
A was move-constructed     ; forwarded in Wrapper<A>'s initializer
A was destroyed            ; the moved-away from copy as the constructor returns
A was move-constructed     ; forwarded in Wrapper<A>'s move-constructor
A was move-assigned        ; move-assignment operator in Wrapper<A>
A was move-assigned        ; not sure about this one... (apparently caused by the Wrapper<A&> move-assignment)
A was destroyed            ; the sub-object of val2
A was destroyed            ; the sub-object of val1
A was destroyed            ; the automatic variable a in main

【讨论】:

  • 任何说 C++ 还没有变成一团糟的人都是疯了。
  • 作业应该如何为有参考成员的班级工作?
  • 您的第一个构造函数使用 std::forward 而不使用通用引用,这并不完全正确(查看 Scott Meyers 关于通用引用的文章,或他的新 Effective Modern C++)。将其更改为 template &lt;typename U&gt; Wrapper(U&amp;&amp; t) : m_t{std::forward&lt;U&gt;(t)} ... 之类的内容将减少一次移动构建和一次破坏。
  • 这对我来说看起来不错。现在最大的问题是:这是否符合您的要求?这是一个只有类设计师才能回答的问题,而不是我或其他任何人。你已经很好地展示了它的作用。干得好。
  • @YongweiWu:如果第一个构造函数按照您的建议进行了更改,则还应约束U,以便std::is_constructible&lt;Wrapper&lt;T&gt;, U&gt;{} 给出正确答案,无论正确答案是什么。如果不加约束,答案总是正确的,这几乎肯定是错误的答案。
猜你喜欢
  • 2013-01-08
  • 2016-01-27
  • 1970-01-01
  • 1970-01-01
  • 2019-05-26
  • 1970-01-01
  • 2019-09-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多