C++一个引用的多个转发:首先复制,然后移动
C++ multiple forwarding of one reference: first copy and then move
请考虑以下代码,其中相同的引用被转发到基类两次并在那里用于构造元组:
template<typename ... Ts>
struct Base
{
template<typename ... _Ts>
Base(_Ts&& ... ts) : tup{std::forward<_Ts>(ts) ...} {}
std::tuple <Ts ...> tup;
};
template<typename T>
struct Derived : public Base<T, T>
{
template<typename _T>
Derived(_T&& t) : Base<T, T>{t, std::forward<_T>(t)} {}
};
首先调用 Derived
中的基类构造函数作为 Base<T, T>{t, std::forward<_T>(t)}
,然后使用 tup{std::forward<Ts>(ts)...}
调用元组构造函数具有以下原因:
当t
是右值引用时,第一个元组参数应传递给 t
的 lvalue-ref,因此通过 t
的副本构造,而第二个元组元素应该获得 rvalue-ref,因此,如果可能的话,使用 move 进行构造。
这种方法似乎得到了SO上的几个问题和答案的支持(例如,这里,这里和这里(,这些问题和答案指出大括号的init列表对其参数进行从左到右的评估。
但是,当我在一个简单的示例中使用上面的代码时,实际行为(始终(与我的预期相反:
struct A
{
A() = default;
A(A const& other) : vec(other.vec) { std::cout<<"copy"<<std::endl; }
A(A && other) : vec(std::move(other.vec)) { std::cout<<"move"<<std::endl; }
std::vector<int> vec = std::vector<int>(100);
};
int main()
{
Derived<A> d(A{}); //prints first "move", then "copy"
std::cout<<std::get<0>(d.tup).vec.size()<<std::endl; //prints 0
std::cout<<std::get<1>(d.tup).vec.size()<<std::endl; //prints 100
}
这是在 Koliru 上使用 gcc 的示例。(gcc编译器显然在这方面曾经有一个错误,但它已经过去大约两年了,应该不再是问题了。
问题:
- 我在这里的实施或假设上哪里错了?
- 如何修复上面的代码以按预期运行:首先复制 - 然后移动?
你应该显式地执行复制,否则你会传递一个引用,稍后由 copy 使用(但是,使用别名,可能已被移动(:
template<typename U>
struct Derived : public Base<U, U>
{
template<typename T>
Derived(T&& t) : Base<U, U>{T(t), std::forward<T>(t)} {}
};
演示
我不确定对象初始化的操作顺序是否重要。由于完美的转发,在调用std::tuple
构造函数之前,实际上不会进行任何复制或移动(即仅传递左值引用和右值引用(。而且,在这一点上,这取决于 std::tuple
的实现细节 .
考虑是否使用了以下my_tup
结构而不是std::tuple
:
template<typename T1, typename T2>
struct my_tup
{
template <typename A, typename B>
my_tup(A&& a, B&& b)
: t1(std::forward<A>(a)), t2(std::forward<B>(b))
{
}
T1 t1;
T2 t2;
};
正如预期的那样,这将打印"复制",然后"移动"(coliru(。但是,如果您拥有:
template<typename T1, typename T2>
struct my_tup
{
template <typename A, typename B>
my_tup(A&& a, B&& b)
: t2(std::forward<B>(b)), t1(std::forward<A>(a))
{
}
T2 t2;
T1 t1;
};
然后打印"移动",然后打印"复制",就像std::tuple
一样(coliru(。
可能是由于可变参数模板的扩展方式,std::tuple
必须以从右到左的方式处理参数。我不确定这是否依赖于实现或在规范中指定。
- 当有分配器意识的容器被复制/移动时,反弹分配器是否被复制/移走
- std::元组分配和复制/移动异常保证
- std::async 如何工作:为什么它会调用这么多次复制/移动?
- 在临时将成员带出时省略复制/移动
- 是否可以避免在以下代码中复制/移动构造函数的需要?
- 默认复制/移动构造函数时 GDB 中的奇怪行为
- 返回 *&object 时是否允许复制/移动省略?
- 删除复制构造函数是否也会删除默认的复制/移动运算符?
- 用于删除复制/移动分配运算符的有效签名
- 复制 elision/RVO 会导致从同一对象复制/移动吗?
- 意外缺少隐式声明的复制/移动构造函数
- 是否可以传递具有捕获的不可复制(移动)值的 lambda
- CRTP 和复制/移动赋值/构造函数继承
- C++复制/移动构造函数和赋值运算符
- 覆盖复制/移动分配超载时,我是否需要删除当前的成员数据
- 为什么编译器在有模板构造函数时生成复制/移动构造函数
- C++:使用引用和值解压缩元组,而无需复制/移动太多
- 从C++中的基类继承复制/移动构造函数作为构造函数
- 初始化 std::数组而不复制/移动元素
- 是否允许复制/移动省略使使用已删除函数的程序格式正确?