提供移动参数的构造函数的惯用方式
Idiomatic way of providing constructors that move their arguments
假设我有以下 class:
#include <vector>
class Foo
{
public:
Foo(const std::vector<int> & a, const std::vector<int> & b)
: a{ a }, b{ b } {}
private:
std::vector<int> a, b;
};
但现在我想考虑构造函数的调用者可能将临时对象传递给它的情况,我想将这些临时对象正确地移动到 a
和 b
.
现在我真的必须再添加 3 个构造函数,其中 1 个具有 a
作为右值引用,其中 1 个具有 b
作为右值引用,而 1 个仅具有右值引用参数?
当然,这个问题可以概括为任何数量的值得移动的参数,所需构造函数的数量将是 arguments^2 2^arguments.
这个问题也适用于所有函数。
执行此操作的惯用方法是什么?还是我在这里完全遗漏了一些重要的东西?
通常的方法是按值传递,然后从参数移动构造成员:
Foo(std::vector<int> a, std::vector<int> b)
: a{ std::move(a) },
b{ std::move(b) }
{}
如果需要副本,它将由调用者创建,然后从中移出以构造成员。如果调用者传递一个临时值(或其他右值),则不会进行任何复制,只会进行一次移动。
对于没有高效移动构造函数的参数,接受对 const 的引用会稍微高效一些,我会保留这一点。
None 如果函数不需要传递值的 copy - 如果不修改则继续使用 const ref值并且不需要它在函数执行结束后继续存在。就个人而言,我在我的构造函数中大量使用按值传递和移动,但很少在我的其他函数中使用。
真的,如果搬家建筑很便宜,你应该按价值计算。
这导致在每种情况下都比理想情况多走 1 步。
但如果你真的必须避免这种情况,你可以这样做:
template<class T>
struct sink_of {
void const* ptr = 0;
T(*fn)(void const*) = 0;
sink_of(T&& t):
ptr( std::addressof(t) ),
fn([](void const*ptr)->T{
return std::move(*(T*)(ptr));
})
{}
sink_of(T const& t):
ptr( std::addressof(t) ),
fn([](void const*ptr)->T{
return *(T*)(ptr);
})
{}
operator T() const&& {
return fn(ptr);
}
};
它使用 RVO/elision 来避免以一堆基于指针的开销和类型擦除为代价的额外移动。
Here 是一些测试代码,证明
test( noisy nin ):n(std::move(nin)) {}
test( sink_of<noisy> nin ):n(std::move(nin)) {}
恰好与 noisy
.
的 1 个移动构造不同
"perfect"版本
test( noisy const& nin ):n(nin) {}
test( noisy && nin ):n(std::move(nin)) {}
或
template<class Noisy, std::enable_if_t<std::is_same<noisy, std::decay_t<Noisy>>{}, int> = 0 >
test( Noisy && nin ):n(std::forward<Noisy>(nin)) {}
与 sink_of
版本的 copy/moves 数量相同。
(noisy
是一种打印有关 moves/copies 它参与的信息的类型,因此您可以看到通过省略优化了哪些内容)
这 只有当多余的 move
需要消除时才值得 。对于 vector
它不是。
另外,如果你有一个 "true temporary" 你通过了,按价值计算的和 sink_of 或 "perfect" 一样好。
假设我有以下 class:
#include <vector>
class Foo
{
public:
Foo(const std::vector<int> & a, const std::vector<int> & b)
: a{ a }, b{ b } {}
private:
std::vector<int> a, b;
};
但现在我想考虑构造函数的调用者可能将临时对象传递给它的情况,我想将这些临时对象正确地移动到 a
和 b
.
现在我真的必须再添加 3 个构造函数,其中 1 个具有 a
作为右值引用,其中 1 个具有 b
作为右值引用,而 1 个仅具有右值引用参数?
当然,这个问题可以概括为任何数量的值得移动的参数,所需构造函数的数量将是 arguments^2 2^arguments.
这个问题也适用于所有函数。
执行此操作的惯用方法是什么?还是我在这里完全遗漏了一些重要的东西?
通常的方法是按值传递,然后从参数移动构造成员:
Foo(std::vector<int> a, std::vector<int> b)
: a{ std::move(a) },
b{ std::move(b) }
{}
如果需要副本,它将由调用者创建,然后从中移出以构造成员。如果调用者传递一个临时值(或其他右值),则不会进行任何复制,只会进行一次移动。
对于没有高效移动构造函数的参数,接受对 const 的引用会稍微高效一些,我会保留这一点。
None 如果函数不需要传递值的 copy - 如果不修改则继续使用 const ref值并且不需要它在函数执行结束后继续存在。就个人而言,我在我的构造函数中大量使用按值传递和移动,但很少在我的其他函数中使用。
真的,如果搬家建筑很便宜,你应该按价值计算。
这导致在每种情况下都比理想情况多走 1 步。
但如果你真的必须避免这种情况,你可以这样做:
template<class T>
struct sink_of {
void const* ptr = 0;
T(*fn)(void const*) = 0;
sink_of(T&& t):
ptr( std::addressof(t) ),
fn([](void const*ptr)->T{
return std::move(*(T*)(ptr));
})
{}
sink_of(T const& t):
ptr( std::addressof(t) ),
fn([](void const*ptr)->T{
return *(T*)(ptr);
})
{}
operator T() const&& {
return fn(ptr);
}
};
它使用 RVO/elision 来避免以一堆基于指针的开销和类型擦除为代价的额外移动。
Here 是一些测试代码,证明
test( noisy nin ):n(std::move(nin)) {}
test( sink_of<noisy> nin ):n(std::move(nin)) {}
恰好与 noisy
.
"perfect"版本
test( noisy const& nin ):n(nin) {}
test( noisy && nin ):n(std::move(nin)) {}
或
template<class Noisy, std::enable_if_t<std::is_same<noisy, std::decay_t<Noisy>>{}, int> = 0 >
test( Noisy && nin ):n(std::forward<Noisy>(nin)) {}
与 sink_of
版本的 copy/moves 数量相同。
(noisy
是一种打印有关 moves/copies 它参与的信息的类型,因此您可以看到通过省略优化了哪些内容)
这 只有当多余的 move
需要消除时才值得 。对于 vector
它不是。
另外,如果你有一个 "true temporary" 你通过了,按价值计算的和 sink_of 或 "perfect" 一样好。