使用 C++11 复制和移动时避免代码重复
Avoid code duplication when using C++11 copy & move
C++11 "move" 是一个很好的特性,但我发现当与 "copy" 同时使用时很难避免代码重复(我们都讨厌这个)。下面的代码是我实现的一个简单的循环队列(不完整),这两个push()方法除了一行之外几乎一样
我 运行 遇到过很多类似的情况。有什么想法如何在不使用宏的情况下避免这种代码重复?
=== 编辑 ===
在这个特定的例子中,重复的代码可以重构出来并放入一个单独的函数中,但有时这种重构不可用或不容易实现。
#include <cstdlib>
#include <utility>
template<typename T>
class CircularQueue {
public:
CircularQueue(long size = 32) : size{size} {
buffer = std::malloc(sizeof(T) * size);
}
~CircularQueue();
bool full() const {
return counter.in - counter.out >= size;
}
bool empty() const {
return counter.in == counter.out;
}
void push(T&& data) {
if (full()) {
throw Invalid{};
}
long offset = counter.in % size;
new (buffer + offset) T{std::forward<T>(data)};
++counter.in;
}
void push(const T& data) {
if (full()) {
throw Invalid{};
}
long offset = counter.in % size;
new (buffer + offset) T{data};
++counter.in;
}
private:
T* buffer;
long size;
struct {
long in, out;
} counter;
};
此处最简单的解决方案是使参数成为转发引用。这样你就可以只用一个函数:
template <class U>
void push(U&& data) {
if (full()) {
throw Invalid{};
}
long offset = counter.in % size;
// please note here we construct a T object (the class template)
// from an U object (the function template)
new (buffer + offset) T{std::forward<U>(data)};
++counter.in;
}
虽然方法有缺点:
它不是通用的,也就是说它不能总是完成(以微不足道的方式)。例如当参数不像T那么简单时(例如SomeType<T>
)。
您延迟了参数的类型检查。当使用错误的参数类型调用 push 时,可能会出现长而看似无关的编译器错误。
顺便说一下,在您的示例中 T&&
不是转发引用。这是一个右值引用。那是因为 T 不是函数的模板参数。它是 class 的,所以在实例化 class 时已经推导出来了。所以编写代码的正确方法应该是:
void push(T&& data) {
...
... T{std::move(data)};
...
}
void push(const T& data) {
... T{data};
...
}
使用转发引用的解决方案是一个很好的解决方案。在某些情况下,它会变得困难或烦人。第一步,用一个采用显式类型的接口包装它,然后在 cpp 文件中将它们发送到模板实现。
现在有时第一步也会失败:如果有 N 个不同的参数都需要转发到容器中,这需要一个大小为 2^N 的接口,并且可能必须跨越多层接口开始实施。
为此,我们可以随身携带最终动作,而不是携带或采用特定类型。在最外层接口,我们将任意类型转换为 that/those 个动作。
template<class T>
struct construct {
T*(*action)(void* state,void* target)=nullptr;
void* state=nullptr;
construct()=default;
construct(T&& t):
action(
[](void*src,void*targ)->T*{
return new(targ) T( std::move(*static_cast<T*>(src)) );
}
),
state(std::addressof(t))
{}
construct(T const& t):
action(
[](void*src,void*targ)->T*{
return new(targ) T( *static_cast<T const*>(src) );
}
),
state(const_cast<void*>(std::addressof(t)))
{}
T*operator()(void* target)&&{
T* r = action(state,target);
*this = {};
return r;
}
explicit operator bool()const{return action;}
construct(construct&&o):
construct(o)
{
action=nullptr;
}
construct& operator=(construct&&o){
*this = o;
o.action = nullptr;
return *this;
}
private:
construct(construct const&)=default;
construct& operator=(construct const&)=default;
};
一旦你有了一个 construct<T> ctor
对象,你可以通过 std::move(ctor)(location)
构造一个 T
的实例,其中 location 是一个正确对齐的指针,用于存储 T
足够的存储空间。
A constructor<T>
可以从右值或左值 T
隐式转换。它也可以通过 emplace 支持得到增强,但这需要更多的样板文件才能正确完成(或者需要更多的开销才能轻松完成)。
Live example。图案是比较简单的类型擦除。我们将操作存储在一个函数指针中,将数据存储在一个空指针中,并从存储的动作函数指针中的空指针重建数据。
上述类型 erasure/runtime 概念技术的成本适中。
我们也可以这样实现:
template<class T>
struct construct :
private std::function< T*(void*) >
{
using base = std::function< T*(void*) >;
construct() = default;
construct(T&& t):base(
[&](void* target)mutable ->T* {
return new(target) T(std::move(t));
}
) {}
construct(T const& t):base(
[&](void* target)->T* {
return new(target) T(t);
}
) {}
T* operator()(void* target)&&{
T* r = base::operator()(target);
(base&)(*this)={};
return r;
}
explicit operator bool()const{
return (bool)static_cast<base const&>(*this);
}
};
这依赖于 std::function
为我们进行类型擦除。
因为它被设计为只工作一次(我们从源代码开始),我强制使用右值上下文并消除我的状态。我还隐藏了我是 std::function 的事实,因为它不遵守这些规则。
前言
在向您的界面添加移动语义支持时引入代码重复非常烦人。对于每个函数,您必须进行两个几乎相同的实现:一个从参数复制,一个从参数移动。如果一个函数有两个参数,它甚至不是代码重复,而是代码四倍:
void Func(const TArg1 &arg1, const TArg2 &arg2); // copies from both arguments
void Func(const TArg1 &arg1, TArg2 &&arg2); // copies from the first, moves from the second
void Func( TArg1 &&arg1, const TArg2 &arg2); // moves from the first, copies from the second
void Func( TArg1 &&arg1, TArg2 &&arg2); // moves from both
在一般情况下,您必须为一个函数重载最多 2^N 次,其中 N 是参数的数量。在我看来,这使得移动语义实际上无法使用。这是 C++11 最令人失望的特性。
问题可能更早发生。我们来看看下面这段代码:
void Func1(const T &arg);
T Func2();
int main()
{
Func1(Func2());
return 0;
}
很奇怪,一个临时的 object 被传递到接受引用的函数中。临时 object 甚至可能没有地址,例如可以将其缓存在寄存器中。但是 C++ 允许在接受 const(且仅 const)引用的地方传递临时变量。在这种情况下,临时对象的生命周期会延长,直到引用的生命周期结束。如果没有这个规则,我们在这里也得做两个实现:
void Func1(const T& arg);
void Func1(T arg);
我不知道为什么创建了允许在接受引用的地方传递临时对象的规则(好吧,如果没有这个规则,我们将无法调用复制构造函数来制作临时对象的副本 object,所以 Func1(Func2())
其中 Func1
是 void Func1(T arg)
无论如何都行不通 :) ),但有了这个规则,我们不必对函数进行两次重载。
解决方案#1:完美转发
不幸的是,没有这样简单的规则可以使同一函数的两个重载变得不必要:一个采用 const 左值引用,另一个采用右值引用。相反 完美转发 被设计
template <typename U>
void Func(U &¶m) // despite the fact the parameter has "U&&" type at declaration,
// it actually can be just "U&" or even "const U&", it’s due to
// the template type deducing rules
{
value = std::forward<U>(param); // use move or copy semantic depending on the
// real type of param
}
它可能看起来像一条简单的规则,可以避免重复。但这并不简单,它使用了不明显的模板"magic"来解决问题,而且这个解决方案也有一些缺点,因为使用完美转发的功能必须模板化:
- 函数的实现必须位于header.
- 它会破坏二进制大小,因为对于每个使用的参数类型组合 (copy/move),它都会生成单独的实现(源代码中只有一个实现,同时最多有 2 个^N 二进制实现)。
- 参数没有类型检查。您可以将任何类型的值传递给函数(因为函数接受模板类型)。实际检查将在实际使用参数的地方进行。这可能会产生 hard-to-understand 错误消息并导致一些意想不到的后果。
最后一个问题可以通过为 perfect-forwarding 函数创建 non-template 包装器来解决:
public:
void push( T &&data) { push_fwd(data); }
void push(const T &data) { push_fwd(data); }
private:
template <typename U>
void push_fwd(U &&data)
{
// actual implementation
}
当然只有在函数参数很少(一个或两个)的情况下才能实际使用。否则你必须制作太多的包装纸(最多 2^N,你知道的)。
解决方案 #2:运行时检查可移动性
最终我想到检查参数的可移动性不应该在 compile-time 而应该在运行时完成。我用构造函数创建了一些 reference-wrapper class,这些构造函数采用两种类型的引用(右值和常量左值)。 class 将传递给构造函数的引用存储为 const 左值引用,此外它还存储传递的引用是否为右值的标志。
然后您可以在运行时检查原始引用是否为右值,如果是,您只需将存储的引用转换为 rvalue-reference.
不出所料,有人比我先想到了这个想法。他将其命名为 "in idiom"(我将其命名为 "pmp" - 可能是可移动的参数)。你可以详细阅读这个习语here and here(关于"in"习语的原始页面,如果你真的对问题感兴趣,我建议阅读文章的所有 3 个部分,文章对问题进行了深入的评论)。
简而言之,成语的实现如下所示:
template <typename T>
class in
{
public:
in (const T& l): v_ (l), rv_ (false) {}
in (T&& r): v_ (r), rv_ (true) {}
bool rvalue () const {return rv_;}
const T& get () const {return v_;}
T&& rget () const {return std::move (const_cast<T&> (v_));}
private:
const T& v_; // original reference
bool rv_; // whether it is rvalue-reference
};
(完整实现还包含某些类型可以隐式转换为 T 的特殊情况)
用法示例:
class A
{
public:
void set_vec(in<std::vector<int>> param1, in<std::vector<int>> param2)
{
if (param1.rvalue()) vec1 = param1.rget(); // move if param1 is rvalue
else vec1 = param1.get(); // just copy otherwise
if (param2.rvalue()) vec2 = param2.rget(); // move if param2 is rvalue
else vec2 = param2.get(); // just copy otherwise
}
private:
std::vector<int> vec1, vec2;
};
"in" 的实现缺少复制和移动构造函数。
class in
{
...
in(const in &other): v_(other.v_), rv_(false) {} // always makes parameter not movable
// even if the original reference
// is movable
in( in &&other): v_(other.v_), rv_(other.rv_) {} // makes parameter movable if the
// original reference was is movable
...
};
现在我们可以这样使用了:
void func1(in<std::vector<int>> param);
void func2(in<std::vector<int>> param);
void func3(in<std::vector<int>> param)
{
func1(param); // don't move param into func1 even if original reference
// is rvalue. func1 will always use copy of param, since we
// still need param in this function
// some usage of param
// now we don’t need param
func2(std::move(param)); // move param into func2 if original reference
// is rvalue, or copy param into func2 if original
// reference is const lvalue
}
我们还可以重载赋值运算符:
template<typename T>
T& operator=(T &lhs, in<T> rhs)
{
if (rhs.rvalue()) lhs = rhs.rget();
else lhs = rhs.get();
return lhs;
}
以后就不用每次都去查ravlue了,直接这样用就可以了:
vec1 = std::move(param1); // moves or copies depending on whether param1 is movable
vec2 = std::move(param2); // moves or copies depending on whether param2 is movable
但不幸的是,C++ 不允许将 operator=
重载为全局函数 (https://whosebug.com/a/871290/5447906)。但是我们可以将这个函数重命名为 assign
:
template<typename T>
void assign(T &lhs, in<T> rhs)
{
if (rhs.rvalue()) lhs = rhs.rget();
else lhs = rhs.get();
}
并像这样使用它:
assign(vec1, std::move(param1)); // moves or copies depending on whether param1 is movable
assign(vec2, std::move(param2)); // moves or copies depending on whether param2 is movable
此外,这不适用于构造函数。我们不能只写:
std::vector<int> vec(std::move(param));
这需要sta支持此功能的 dard 库:
class vector
{
...
public:
vector(std::in<vector> other); // copy and move constructor
...
}
但是标准对我们的"in"class一无所知。而这里我们无法做出类似于assign
的解决方法,所以"in"class的使用受到限制。
后记
T
, const T&
, T&&
参数对我来说太多了。停止介绍功能相同(好吧,几乎相同)的东西。 T
就够了!
我更愿意这样写:
// The function in ++++C language:
func(std::vector<int> param) // no need to specify const & or &&, param is just parameter.
// it is always reference for complex types (or for types with
// special qualifier that says that arguments of this type
// must be always passed by reference).
{
another_vec = std::move(param); // move parameter if it's movable.
// compiler hides actual rvalue-ness
// of the arguments in its ABI
}
我不知道标准委员会是否考虑过这种移动语义实现,但在 C++ 中进行此类更改可能为时已晚,因为它们会使编译器的 ABI 与以前的版本不兼容。而且它增加了一些运行时开销,并且可能还有其他我们不知道的问题。
C++11 "move" 是一个很好的特性,但我发现当与 "copy" 同时使用时很难避免代码重复(我们都讨厌这个)。下面的代码是我实现的一个简单的循环队列(不完整),这两个push()方法除了一行之外几乎一样
我 运行 遇到过很多类似的情况。有什么想法如何在不使用宏的情况下避免这种代码重复?
=== 编辑 ===
在这个特定的例子中,重复的代码可以重构出来并放入一个单独的函数中,但有时这种重构不可用或不容易实现。
#include <cstdlib>
#include <utility>
template<typename T>
class CircularQueue {
public:
CircularQueue(long size = 32) : size{size} {
buffer = std::malloc(sizeof(T) * size);
}
~CircularQueue();
bool full() const {
return counter.in - counter.out >= size;
}
bool empty() const {
return counter.in == counter.out;
}
void push(T&& data) {
if (full()) {
throw Invalid{};
}
long offset = counter.in % size;
new (buffer + offset) T{std::forward<T>(data)};
++counter.in;
}
void push(const T& data) {
if (full()) {
throw Invalid{};
}
long offset = counter.in % size;
new (buffer + offset) T{data};
++counter.in;
}
private:
T* buffer;
long size;
struct {
long in, out;
} counter;
};
此处最简单的解决方案是使参数成为转发引用。这样你就可以只用一个函数:
template <class U>
void push(U&& data) {
if (full()) {
throw Invalid{};
}
long offset = counter.in % size;
// please note here we construct a T object (the class template)
// from an U object (the function template)
new (buffer + offset) T{std::forward<U>(data)};
++counter.in;
}
虽然方法有缺点:
它不是通用的,也就是说它不能总是完成(以微不足道的方式)。例如当参数不像T那么简单时(例如
SomeType<T>
)。您延迟了参数的类型检查。当使用错误的参数类型调用 push 时,可能会出现长而看似无关的编译器错误。
顺便说一下,在您的示例中 T&&
不是转发引用。这是一个右值引用。那是因为 T 不是函数的模板参数。它是 class 的,所以在实例化 class 时已经推导出来了。所以编写代码的正确方法应该是:
void push(T&& data) {
...
... T{std::move(data)};
...
}
void push(const T& data) {
... T{data};
...
}
使用转发引用的解决方案是一个很好的解决方案。在某些情况下,它会变得困难或烦人。第一步,用一个采用显式类型的接口包装它,然后在 cpp 文件中将它们发送到模板实现。
现在有时第一步也会失败:如果有 N 个不同的参数都需要转发到容器中,这需要一个大小为 2^N 的接口,并且可能必须跨越多层接口开始实施。
为此,我们可以随身携带最终动作,而不是携带或采用特定类型。在最外层接口,我们将任意类型转换为 that/those 个动作。
template<class T>
struct construct {
T*(*action)(void* state,void* target)=nullptr;
void* state=nullptr;
construct()=default;
construct(T&& t):
action(
[](void*src,void*targ)->T*{
return new(targ) T( std::move(*static_cast<T*>(src)) );
}
),
state(std::addressof(t))
{}
construct(T const& t):
action(
[](void*src,void*targ)->T*{
return new(targ) T( *static_cast<T const*>(src) );
}
),
state(const_cast<void*>(std::addressof(t)))
{}
T*operator()(void* target)&&{
T* r = action(state,target);
*this = {};
return r;
}
explicit operator bool()const{return action;}
construct(construct&&o):
construct(o)
{
action=nullptr;
}
construct& operator=(construct&&o){
*this = o;
o.action = nullptr;
return *this;
}
private:
construct(construct const&)=default;
construct& operator=(construct const&)=default;
};
一旦你有了一个 construct<T> ctor
对象,你可以通过 std::move(ctor)(location)
构造一个 T
的实例,其中 location 是一个正确对齐的指针,用于存储 T
足够的存储空间。
A constructor<T>
可以从右值或左值 T
隐式转换。它也可以通过 emplace 支持得到增强,但这需要更多的样板文件才能正确完成(或者需要更多的开销才能轻松完成)。
Live example。图案是比较简单的类型擦除。我们将操作存储在一个函数指针中,将数据存储在一个空指针中,并从存储的动作函数指针中的空指针重建数据。
上述类型 erasure/runtime 概念技术的成本适中。
我们也可以这样实现:
template<class T>
struct construct :
private std::function< T*(void*) >
{
using base = std::function< T*(void*) >;
construct() = default;
construct(T&& t):base(
[&](void* target)mutable ->T* {
return new(target) T(std::move(t));
}
) {}
construct(T const& t):base(
[&](void* target)->T* {
return new(target) T(t);
}
) {}
T* operator()(void* target)&&{
T* r = base::operator()(target);
(base&)(*this)={};
return r;
}
explicit operator bool()const{
return (bool)static_cast<base const&>(*this);
}
};
这依赖于 std::function
为我们进行类型擦除。
因为它被设计为只工作一次(我们从源代码开始),我强制使用右值上下文并消除我的状态。我还隐藏了我是 std::function 的事实,因为它不遵守这些规则。
前言
在向您的界面添加移动语义支持时引入代码重复非常烦人。对于每个函数,您必须进行两个几乎相同的实现:一个从参数复制,一个从参数移动。如果一个函数有两个参数,它甚至不是代码重复,而是代码四倍:
void Func(const TArg1 &arg1, const TArg2 &arg2); // copies from both arguments
void Func(const TArg1 &arg1, TArg2 &&arg2); // copies from the first, moves from the second
void Func( TArg1 &&arg1, const TArg2 &arg2); // moves from the first, copies from the second
void Func( TArg1 &&arg1, TArg2 &&arg2); // moves from both
在一般情况下,您必须为一个函数重载最多 2^N 次,其中 N 是参数的数量。在我看来,这使得移动语义实际上无法使用。这是 C++11 最令人失望的特性。
问题可能更早发生。我们来看看下面这段代码:
void Func1(const T &arg);
T Func2();
int main()
{
Func1(Func2());
return 0;
}
很奇怪,一个临时的 object 被传递到接受引用的函数中。临时 object 甚至可能没有地址,例如可以将其缓存在寄存器中。但是 C++ 允许在接受 const(且仅 const)引用的地方传递临时变量。在这种情况下,临时对象的生命周期会延长,直到引用的生命周期结束。如果没有这个规则,我们在这里也得做两个实现:
void Func1(const T& arg);
void Func1(T arg);
我不知道为什么创建了允许在接受引用的地方传递临时对象的规则(好吧,如果没有这个规则,我们将无法调用复制构造函数来制作临时对象的副本 object,所以 Func1(Func2())
其中 Func1
是 void Func1(T arg)
无论如何都行不通 :) ),但有了这个规则,我们不必对函数进行两次重载。
解决方案#1:完美转发
不幸的是,没有这样简单的规则可以使同一函数的两个重载变得不必要:一个采用 const 左值引用,另一个采用右值引用。相反 完美转发 被设计
template <typename U>
void Func(U &¶m) // despite the fact the parameter has "U&&" type at declaration,
// it actually can be just "U&" or even "const U&", it’s due to
// the template type deducing rules
{
value = std::forward<U>(param); // use move or copy semantic depending on the
// real type of param
}
它可能看起来像一条简单的规则,可以避免重复。但这并不简单,它使用了不明显的模板"magic"来解决问题,而且这个解决方案也有一些缺点,因为使用完美转发的功能必须模板化:
- 函数的实现必须位于header.
- 它会破坏二进制大小,因为对于每个使用的参数类型组合 (copy/move),它都会生成单独的实现(源代码中只有一个实现,同时最多有 2 个^N 二进制实现)。
- 参数没有类型检查。您可以将任何类型的值传递给函数(因为函数接受模板类型)。实际检查将在实际使用参数的地方进行。这可能会产生 hard-to-understand 错误消息并导致一些意想不到的后果。
最后一个问题可以通过为 perfect-forwarding 函数创建 non-template 包装器来解决:
public:
void push( T &&data) { push_fwd(data); }
void push(const T &data) { push_fwd(data); }
private:
template <typename U>
void push_fwd(U &&data)
{
// actual implementation
}
当然只有在函数参数很少(一个或两个)的情况下才能实际使用。否则你必须制作太多的包装纸(最多 2^N,你知道的)。
解决方案 #2:运行时检查可移动性
最终我想到检查参数的可移动性不应该在 compile-time 而应该在运行时完成。我用构造函数创建了一些 reference-wrapper class,这些构造函数采用两种类型的引用(右值和常量左值)。 class 将传递给构造函数的引用存储为 const 左值引用,此外它还存储传递的引用是否为右值的标志。 然后您可以在运行时检查原始引用是否为右值,如果是,您只需将存储的引用转换为 rvalue-reference.
不出所料,有人比我先想到了这个想法。他将其命名为 "in idiom"(我将其命名为 "pmp" - 可能是可移动的参数)。你可以详细阅读这个习语here and here(关于"in"习语的原始页面,如果你真的对问题感兴趣,我建议阅读文章的所有 3 个部分,文章对问题进行了深入的评论)。
简而言之,成语的实现如下所示:
template <typename T>
class in
{
public:
in (const T& l): v_ (l), rv_ (false) {}
in (T&& r): v_ (r), rv_ (true) {}
bool rvalue () const {return rv_;}
const T& get () const {return v_;}
T&& rget () const {return std::move (const_cast<T&> (v_));}
private:
const T& v_; // original reference
bool rv_; // whether it is rvalue-reference
};
(完整实现还包含某些类型可以隐式转换为 T 的特殊情况)
用法示例:
class A
{
public:
void set_vec(in<std::vector<int>> param1, in<std::vector<int>> param2)
{
if (param1.rvalue()) vec1 = param1.rget(); // move if param1 is rvalue
else vec1 = param1.get(); // just copy otherwise
if (param2.rvalue()) vec2 = param2.rget(); // move if param2 is rvalue
else vec2 = param2.get(); // just copy otherwise
}
private:
std::vector<int> vec1, vec2;
};
"in" 的实现缺少复制和移动构造函数。
class in
{
...
in(const in &other): v_(other.v_), rv_(false) {} // always makes parameter not movable
// even if the original reference
// is movable
in( in &&other): v_(other.v_), rv_(other.rv_) {} // makes parameter movable if the
// original reference was is movable
...
};
现在我们可以这样使用了:
void func1(in<std::vector<int>> param);
void func2(in<std::vector<int>> param);
void func3(in<std::vector<int>> param)
{
func1(param); // don't move param into func1 even if original reference
// is rvalue. func1 will always use copy of param, since we
// still need param in this function
// some usage of param
// now we don’t need param
func2(std::move(param)); // move param into func2 if original reference
// is rvalue, or copy param into func2 if original
// reference is const lvalue
}
我们还可以重载赋值运算符:
template<typename T>
T& operator=(T &lhs, in<T> rhs)
{
if (rhs.rvalue()) lhs = rhs.rget();
else lhs = rhs.get();
return lhs;
}
以后就不用每次都去查ravlue了,直接这样用就可以了:
vec1 = std::move(param1); // moves or copies depending on whether param1 is movable
vec2 = std::move(param2); // moves or copies depending on whether param2 is movable
但不幸的是,C++ 不允许将 operator=
重载为全局函数 (https://whosebug.com/a/871290/5447906)。但是我们可以将这个函数重命名为 assign
:
template<typename T>
void assign(T &lhs, in<T> rhs)
{
if (rhs.rvalue()) lhs = rhs.rget();
else lhs = rhs.get();
}
并像这样使用它:
assign(vec1, std::move(param1)); // moves or copies depending on whether param1 is movable
assign(vec2, std::move(param2)); // moves or copies depending on whether param2 is movable
此外,这不适用于构造函数。我们不能只写:
std::vector<int> vec(std::move(param));
这需要sta支持此功能的 dard 库:
class vector
{
...
public:
vector(std::in<vector> other); // copy and move constructor
...
}
但是标准对我们的"in"class一无所知。而这里我们无法做出类似于assign
的解决方法,所以"in"class的使用受到限制。
后记
T
, const T&
, T&&
参数对我来说太多了。停止介绍功能相同(好吧,几乎相同)的东西。 T
就够了!
我更愿意这样写:
// The function in ++++C language:
func(std::vector<int> param) // no need to specify const & or &&, param is just parameter.
// it is always reference for complex types (or for types with
// special qualifier that says that arguments of this type
// must be always passed by reference).
{
another_vec = std::move(param); // move parameter if it's movable.
// compiler hides actual rvalue-ness
// of the arguments in its ABI
}
我不知道标准委员会是否考虑过这种移动语义实现,但在 C++ 中进行此类更改可能为时已晚,因为它们会使编译器的 ABI 与以前的版本不兼容。而且它增加了一些运行时开销,并且可能还有其他我们不知道的问题。