在此上下文中完美转发和std :: move行为

时间:2017-03-14 15:51:48

标签: c++ c++11 variadic-templates

我是C ++新手,我想了解完美转发如何与std::move一起使用。

我使用模板函数std::vector<QueueData> queue()定义要填充的fillWithData。由于我花了一些时间研究完美的转发,我首先要检查我是否理解正确,其次要弄清楚这个背景下的move行为是什么。

fillWithData是一个可变参数模板函数,由于forward,它可以通过折叠规则将参数视为左值或右值。 ( Q1 - 这是正确的吗?)

template< class Container, typename ... Args >
static void fillWithData(Container & oDataContainer, 
                         Args&& ... args)  // universal reference
{
    typedef typename Container::value_type::element_type QueueDataPtr;
    oDataContainer.emplace_back(std::forward<Args>(args)...);
}

现在,假设有一个对象User usr,可以将参数传递给fillWithData

如果我拨打fillWithData( queue, usr.getName(), usr.getEmail(), usr.getAddr() )emplace会拨打以下电话吗?

  • 临时QueueData对象的构造函数;
  • 为向量中实际分配的对象移动构造函数(如果为临时定义了一个,否则为复制构造函数);
  • 临时的析构函数;

如果我想进行优化,我可以利用move将左值作为右值( Q2 - usr.getName()左值?):

fillWithData( queue, std::move(usr.getName()), std::move(usr.getEmail()), std::move(usr.getAddr()) )

这样,( Q3 )是直接转发给User构造函数的值,是在向量中直接构造的新对象吗?

此外( Q4 ),例如,如果我在调用usr.getName()之后对fillWithData进行了新的调用,数据是否仍然可用? (我想我会遇到运行时错误。)

最后( Q5 ),没有std::move,实现完美转发是否有意义?

感谢。

1 个答案:

答案 0 :(得分:3)

std::move无法执行完美转发。它将l值引用转换为r值引用。由于它已经执行了强制转换,因此输入与输出不同 - 因此并不完美。

std::forward<X>执行完美转发,但它要求推导出X的类型(即它是当前函数的模板参数,并且它是一个通用引用)。

有人可能会问为什么必须在看似r值的引用变量名称上调用std::move

这是,因为任何带有名称的变量实际上都是l值,无论它是用一个&还是一个int&& x = y();声明的。

所以...

int& x = y();是一个l值,它只能绑定到r值(y必须返回r值引用或临时值)。

std::move(x)也是一个l值,但它将绑定到l值或r值(y可以返回int&amp;或int&amp;&amp;)。

在上述情况下,std::forward<>返回r值引用。在第一种情况下,它(似乎)是完美的,在第二种情况下它会被投射 - 因此不能完美地转发。

std::move检测到差异,而std::forward<T>(x)则没有。

总之,在推导的上下文中,std::move将返回与函数中传递的x完全相同的类别(l-valuenes或r-valueness)。参数,而template<class T> void bar(T x); // pass by value 将始终返回r值引用,即使传递了l值引用。

困惑?这是一个简单的经验法则:

假设:

template<class Deduced> void foo(Deduced&& x)
{
    // x is a universal reference in deduced context, 
    // so we probably want to forward it.
    bar(std::forward<Deduced>(x));
}

void foo(Known&& x)
{
    // x is definitely an r-value reference. No point forwarding something
    // we already know the category of - move it.
    bar(std::move(x));
}

void foo(Known x)
{
    // x is definitely an l-value. No point forwarding an l-value
    // reference, as this will cause an un-necessary copy. 
    // So we must cast x.
    bar(std::move(x));
}

void foo(Known const& x)
{
    // x is definitely a const l-value reference. No point forwarding 
    // a const reference, and no point moving it, since a 
    // (Known const &&) is not useful.
    // Whatever we do, there's going to be a copy.
    bar(x);
}

然后:

main.py