我刚听完Scott Meyers关于C++0x的软件工程广播播客采访。大多数新特性对我来说都是有意义的,我现在对C++0x非常兴奋,只有一个例外。我仍然不懂移动语义。。。到底是什么?
当前回答
移动语义是指在没有人需要源值时转移资源,而不是复制资源。
在C++03中,对象经常被复制,只有在任何代码再次使用该值之前才会被销毁或赋值。例如,当您从函数中按值返回时,除非RVO踢入您返回的值,否则该值将被复制到调用方的堆栈帧,然后超出范围并被销毁。这只是众多示例中的一个:当源对象是临时对象时,请参阅passbyvalue;当超过其capacity()时,可以使用排序等算法重新排列项目。
当这种复制/销毁对很昂贵时,通常是因为对象拥有一些重量级资源。例如,vector<string>可能拥有一个动态分配的内存块,其中包含一个字符串对象数组,每个字符串对象都有自己的动态内存。复制这样的对象代价很高:必须为源中每个动态分配的块分配新的内存,并复制所有值。然后需要释放刚才复制的所有内存。然而,移动一个大向量<string>意味着只需将几个指针(指动态内存块)复制到目标,并在源中将它们清零。
其他回答
这就像复制语义,但不必复制所有数据,而是从“移动”的对象中窃取数据。
我写这个是为了确保我能正确理解。
创建移动语义是为了避免不必要地复制大型对象。Bjarne Stroustrup在其著作《C++编程语言》中使用了两个默认情况下发生不必要复制的示例:一个是交换两个大对象,另一个是从方法返回一个大对象。
交换两个大对象通常涉及将第一个对象复制到临时对象,将第二个对象复制给第一个对象,以及将临时对象复制到第二个。对于内置类型,这非常快,但对于大型对象,这三个副本可能需要大量时间。“移动赋值”允许程序员重写默认的复制行为,而是交换对对象的引用,这意味着根本没有复制,而且交换操作更快。可以通过调用std::move()方法来调用移动赋值。
默认情况下,从方法返回对象涉及在调用方可访问的位置复制本地对象及其关联数据(因为调用方无法访问本地对象,并且在方法完成时会消失)。当返回内置类型时,此操作非常快,但如果返回大型对象,则可能需要很长时间。移动构造函数允许程序员重写此默认行为,并通过将返回给调用者的对象指向与本地对象关联的堆数据来“重用”与本地对象相关的堆数据。因此不需要复制。
在不允许创建本地对象(即堆栈上的对象)的语言中,这些类型的问题不会发生,因为所有对象都分配在堆上,并且总是通过引用访问。
如果你真的对移动语义的一个好的、深入的解释感兴趣,我强烈建议你阅读关于它们的原始论文《向C++语言添加移动语义支持的建议》
这本书非常容易阅读和阅读,为他们提供的好处提供了极好的理由。WG21网站上还有其他关于移动语义的最新论文,但这篇文章可能是最直接的,因为它从顶层的角度处理问题,并没有深入到语言的细节。
我的第一个回答是对移动语义的极其简化的介绍,为了保持简单,特意省略了许多细节。然而,还有很多东西需要移动语义,我认为是时候用第二个答案来填补空白了。第一个答案已经很老了,简单地用完全不同的文本替换它并不合适。我认为它仍然可以作为第一个介绍。但如果你想深入了解,请继续阅读:)
Stephan T.Lavavej花时间提供了宝贵的反馈。非常感谢你,斯蒂芬!
介绍
移动语义允许对象在特定条件下拥有其他对象的外部资源。这在两方面很重要:
把昂贵的复制品变成廉价的移动。请参见我的第一个答案。注意,如果一个对象不管理至少一个外部资源(直接或通过其成员对象间接),那么移动语义将不会比复制语义提供任何优势。在这种情况下,复制对象和移动对象意味着完全相同的事情:类cannot_benefit_from_move_semantics{int a;//移动int意味着复制int浮动b;//移动浮动意味着复制浮动双c;//移动替身意味着复制替身字符d[64];//移动char数组意味着复制char数组// ...};实施安全的“仅移动”类型;也就是说,对于复制没有意义,但移动有意义的类型。示例包括具有唯一所有权语义的锁、文件句柄和智能指针。注意:这个答案讨论了std::auto_ptr,这是一个已弃用的C++98标准库模板,在C++11中被std::unique_ptr替换。中级C++程序员可能至少对std::auto_ptr有点熟悉,因为它显示了“移动语义”,这似乎是讨论C++11中移动语义的一个很好的起点。YMMV。
什么是移动?
C++98标准库提供了一个具有唯一所有权语义的智能指针,称为std::auto_ptr<T>。如果您不熟悉auto_ptr,其目的是确保始终释放动态分配的对象,即使在遇到异常时也是如此:
{
std::auto_ptr<Shape> a(new Triangle);
// ...
// arbitrary code, could throw exceptions
// ...
} // <--- when a goes out of scope, the triangle is deleted automatically
auto_ptr的不寻常之处在于它的“复制”行为:
auto_ptr<Shape> a(new Triangle);
+---------------+
| triangle data |
+---------------+
^
|
|
|
+-----|---+
| +-|-+ |
a | p | | | |
| +---+ |
+---------+
auto_ptr<Shape> b(a);
+---------------+
| triangle data |
+---------------+
^
|
+----------------------+
|
+---------+ +-----|---+
| +---+ | | +-|-+ |
a | p | | | b | p | | | |
| +---+ | | +---+ |
+---------+ +---------+
注意,用a初始化b并不会复制三角形,而是将三角形的所有权从a转移到b。我们还说“a移动到b”或“三角形从a移动到了b”。这听起来可能令人困惑,因为三角形本身在记忆中总是停留在同一个位置。
移动对象意味着将其管理的某些资源的所有权转移到另一个对象。
auto_ptr的复制构造函数可能看起来像这样(有些简化):
auto_ptr(auto_ptr& source) // note the missing const
{
p = source.p;
source.p = 0; // now the source no longer owns the object
}
危险无害的移动
auto_ptr的危险之处在于,语法上看起来像副本的东西实际上是移动。尝试对从auto_ptr移动的对象调用成员函数将调用未定义的行为,因此必须非常小心,不要在从以下对象移动后使用auto_ptr:
auto_ptr<Shape> a(new Triangle); // create triangle
auto_ptr<Shape> b(a); // move a into b
double area = a->area(); // undefined behavior
但auto_ptr并不总是危险的。工厂函数是auto_ptr的完美用例:
auto_ptr<Shape> make_triangle()
{
return auto_ptr<Shape>(new Triangle);
}
auto_ptr<Shape> c(make_triangle()); // move temporary into c
double area = make_triangle()->area(); // perfectly safe
注意两个示例如何遵循相同的语法模式:
auto_ptr<Shape> variable(expression);
double area = expression->area();
然而,其中一个调用未定义的行为,而另一个没有。那么表达式a和make_triangle()之间有什么区别?他们不是同一类型吗?的确如此,但它们有不同的价值类别。
价值类别
显然,表示auto_ptr变量的表达式a和表示按值返回auto_ptr的函数的调用的表达式make_triangle()之间一定有一些深刻的区别,因此每次调用时都会创建一个新的临时auto_ptr对象。a是左值的示例,而maketriangle()是右值的示例。
从左值(如a)移动是危险的,因为我们可以稍后尝试通过调用未定义的行为来调用成员函数。另一方面,从诸如make_triangle()之类的右值移动是完全安全的,因为在复制构造函数完成其任务后,我们不能再次使用临时值。没有表示所述临时的表达式;如果我们再次简单地编写maketriangle(),就会得到一个不同的临时变量。事实上,从临时移动的已在下一行中删除:
auto_ptr<Shape> c(make_triangle());
^ the moved-from temporary dies right here
请注意,字母l和r在赋值的左侧和右侧具有历史渊源。这在C++中不再是正确的,因为有些左值不能出现在赋值的左侧(比如没有赋值运算符的数组或用户定义的类型),而有些右值可以出现(带有赋值运算符的类类型的所有右值)。
类类型的右值是一个表达式,它的求值将创建一个临时对象。在正常情况下,同一范围内没有其他表达式表示同一临时对象。
Rvalue参考
我们现在明白从左值转移是潜在的危险,但从右值转移是无害的。如果C++有语言支持来区分左值参数和右值参数,那么我们要么完全禁止从左值移动,要么至少在调用点明确从左值进行移动,这样我们就不会再意外地移动了。
C++11对这个问题的答案是右值引用。右值引用是一种仅绑定到右值的新引用,语法为X&&。好的旧引用X&现在被称为左值引用。(注意,X&&不是对引用的引用;C++中没有这样的东西。)
如果我们将const放入混合,我们已经有四种不同的引用。它们可以绑定到哪些类型的X表达式?
lvalue const lvalue rvalue const rvalue
---------------------------------------------------------
X& yes
const X& yes yes yes yes
X&& yes
const X&& yes yes
在实践中,您可以忘记常量X&&。限制从右值读取不是很有用。
右值引用X&&是一种仅绑定到右值的新引用。
隐式转换
Rvalue引用经历了几个版本。从版本2.1开始,如果存在从Y到X的隐式转换,则右值引用X&&也绑定到不同类型Y的所有值类别。在这种情况下,创建了类型X的临时值,并且右值引用绑定到该临时值:
void some_function(std::string&& r);
some_function("hello world");
在上面的示例中,“helloworld”是constchar[12]类型的左值。由于存在从constchar[12]到constchar*到std::string的隐式转换,因此创建了一个std::字符串类型的临时变量,并将r绑定到该临时变量。这是右值(表达式)和临时值(对象)之间的区别有点模糊的情况之一。
移动构造函数
带有X&&参数的函数的一个有用示例是移动构造函数X::X(X&&source)。其目的是将托管资源的所有权从源转移到当前对象。
在C++11中,std::auto_ptr<T>已被std::unique_ptr<T>所取代,它利用了右值引用。我将开发并讨论unique_ptr的简化版本。首先,我们封装一个原始指针并重载运算符->和*,因此我们的类感觉像一个指针:
template<typename T>
class unique_ptr
{
T* ptr;
public:
T* operator->() const
{
return ptr;
}
T& operator*() const
{
return *ptr;
}
构造函数获取对象的所有权,析构函数将其删除:
explicit unique_ptr(T* p = nullptr)
{
ptr = p;
}
~unique_ptr()
{
delete ptr;
}
现在有一个有趣的部分,移动构造函数:
unique_ptr(unique_ptr&& source) // note the rvalue reference
{
ptr = source.ptr;
source.ptr = nullptr;
}
此移动构造函数与auto_ptr复制构造函数完全相同,但只能提供右值:
unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a); // error
unique_ptr<Shape> c(make_triangle()); // okay
第二行无法编译,因为a是左值,但参数unique_ptr&&source只能绑定到右值。这正是我们想要的;危险的举动绝不应该是含蓄的。第三行编译得很好,因为make_triangle()是一个右值。move构造函数将所有权从临时转移到c。再次,这正是我们想要的。
move构造函数将托管资源的所有权转移到当前对象中。
移动分配运算符
最后一个缺失的部分是移动赋值运算符。它的任务是释放旧资源并从其论证中获取新资源:
unique_ptr& operator=(unique_ptr&& source) // note the rvalue reference
{
if (this != &source) // beware of self-assignment
{
delete ptr; // release the old resource
ptr = source.ptr; // acquire the new resource
source.ptr = nullptr;
}
return *this;
}
};
注意,移动赋值运算符的这个实现是如何复制析构函数和移动构造函数的逻辑的。你熟悉“复制和交换”这个成语吗?它还可以应用于移动语义,如移动和交换习惯用法:
unique_ptr& operator=(unique_ptr source) // note the missing reference
{
std::swap(ptr, source.ptr);
return *this;
}
};
现在source是unique_ptr类型的变量,它将由move构造函数初始化;也就是说,参数将移动到参数中。参数仍然需要是右值,因为移动构造函数本身有一个右值引用参数。当控制流到达运算符=的右括号时,源超出范围,自动释放旧资源。
移动分配运算符将托管资源的所有权转移到当前对象,释放旧资源。移动和交换习惯用法简化了实现。
从lvalues移动
有时,我们想从左值转移。也就是说,有时我们希望编译器将左值视为右值,这样它就可以调用move构造函数,即使它可能不安全。为此,C++11在header<utility>中提供了一个名为std::move的标准库函数模板。这个名称有点不幸,因为std::move只是将左值转换为右值;它自己不会移动任何东西。它只允许移动。也许它应该被命名为std::cast_to_value或std::enable_move,但我们现在还停留在这个名称上。
以下是如何显式地从左值移动:
unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a); // still an error
unique_ptr<Shape> c(std::move(a)); // okay
注意,在第三行之后,a不再拥有三角形。没关系,因为通过显式地编写std::move(a),我们明确了我们的意图:“亲爱的构造函数,为了初始化c,对a做任何你想做的事情;我不再关心a了。请随意使用a。”
move(somevalue)将左值转换为右值,从而启用后续移动。
X值
注意,即使std::move(a)是一个右值,它的求值也不会创建一个临时对象。这个难题迫使委员会引入第三种价值类别。可以绑定到右值引用的东西,即使它不是传统意义上的右值,也被称为xvalue(eXpiring值)。传统的右值被重命名为prvalues(纯右值)。
prvalue和xvalue都是右值。Xvalue和lvalues都是glvalue(广义lvalues)。用图表更容易掌握这些关系:
expressions
/ \
/ \
/ \
glvalues rvalues
/ \ / \
/ \ / \
/ \ / \
lvalues xvalues prvalues
注意,只有xvalue是真正新的;剩下的只是由于重命名和分组。
C++98的右值在C++11中称为prvalue。将前面段落中出现的所有“右值”替换为“prvalue”。
移出功能
到目前为止,我们已经看到了局部变量和函数参数的变化。但也可以朝相反的方向移动。如果函数按值返回,则调用位置的某些对象(可能是局部变量或临时对象,但可以是任何类型的对象)将使用return语句后的表达式初始化,作为移动构造函数的参数:
unique_ptr<Shape> make_triangle()
{
return unique_ptr<Shape>(new Triangle);
} \-----------------------------/
|
| temporary is moved into c
|
v
unique_ptr<Shape> c(make_triangle());
也许令人惊讶的是,自动对象(未声明为静态的局部变量)也可以隐式移出函数:
unique_ptr<Shape> make_square()
{
unique_ptr<Shape> result(new Square);
return result; // note the missing std::move
}
为什么move构造函数接受左值结果作为参数?结果的范围即将结束,它将在堆栈展开期间被销毁。之后,没有人会抱怨结果发生了某种变化;当控制流返回到调用者时,结果不再存在!因此,C++11有一个特殊的规则,允许从函数返回自动对象,而不必编写std::move。事实上,您不应该使用std::move将自动对象移出函数,因为这会抑制“命名返回值优化”(NRVO)。
切勿使用std::move将自动对象移出函数。
注意,在两个工厂函数中,返回类型都是值,而不是右值引用。Rvalue引用仍然是引用,一如既往,您永远不应该返回对自动对象的引用;如果你欺骗编译器接受你的代码,调用方最终会得到一个悬空引用,如下所示:
unique_ptr<Shape>&& flawed_attempt() // DO NOT DO THIS!
{
unique_ptr<Shape> very_bad_idea(new Square);
return std::move(very_bad_idea); // WRONG!
}
从不通过右值引用返回自动对象。移动仅由移动构造函数执行,而不是通过std::move执行,也不是通过将右值绑定到右值引用来执行。
移入成员
迟早,你会编写这样的代码:
class Foo
{
unique_ptr<Shape> member;
public:
Foo(unique_ptr<Shape>&& parameter)
: member(parameter) // error
{}
};
基本上,编译器会抱怨参数是左值。如果您查看它的类型,您会看到一个右值引用,但右值引用仅仅意味着“绑定到右值的引用”;这并不意味着引用本身就是一个右值!实际上,parameter只是一个具有名称的普通变量。您可以在构造函数的主体内任意频繁地使用参数,并且它始终表示相同的对象。含蓄地离开它是危险的,因此语言禁止这样做。
命名的右值引用是左值,就像任何其他变量一样。
解决方案是手动启用移动:
class Foo
{
unique_ptr<Shape> member;
public:
Foo(unique_ptr<Shape>&& parameter)
: member(std::move(parameter)) // note the std::move
{}
};
您可以辩称,在初始化成员后,参数不再使用。为什么没有特殊的规则可以像使用返回值一样默默地插入std::move?可能是因为这会给编译器实现者带来太多负担。例如,如果构造函数主体在另一个翻译单元中呢?相比之下,返回值规则只需检查符号表即可确定return关键字之后的标识符是否表示自动对象。
也可以按值传递参数。对于unique_ptr这样的仅移动类型,似乎还没有确定的习惯用法。就我个人而言,我更喜欢传递值,因为它会减少界面中的混乱。
特殊成员功能
C++98隐式地按需声明三个特殊的成员函数,即在某个地方需要它们时:复制构造函数、复制赋值运算符和析构函数。
X::X(const X&); // copy constructor
X& X::operator=(const X&); // copy assignment operator
X::~X(); // destructor
Rvalue引用经历了几个版本。从版本3.0开始,C++11根据需要声明了两个额外的特殊成员函数:移动构造函数和移动赋值运算符。请注意,VC10和VC11都不符合3.0版本,因此您必须自己实现它们。
X::X(X&&); // move constructor
X& X::operator=(X&&); // move assignment operator
这两个新的特殊成员函数只有在没有手动声明任何特殊成员函数时才隐式声明。此外,如果声明自己的移动构造函数或移动赋值运算符,则复制构造函数和复制赋值运算符都不会隐式声明。
这些规则在实践中意味着什么?
如果您编写一个没有非托管资源的类,则无需自己声明五个特殊成员函数中的任何一个,您将免费获得正确的复制语义和移动语义。否则,您必须自己实现特殊的成员函数。当然,如果您的类没有从移动语义中受益,则无需实现特殊的移动操作。
请注意,复制赋值运算符和移动赋值运算符可以合并为一个统一的赋值运算符,按值取值:
X& X::operator=(X source) // unified assignment operator
{
swap(source); // see my first answer for an explanation
return *this;
}
这样,要实现的特殊成员函数的数量从5个减少到4个。在异常安全性和效率之间存在权衡,但我不是这个问题的专家。
转发引用(以前称为通用引用)
考虑以下函数模板:
template<typename T>
void foo(T&&);
您可能希望T&&只绑定到右值,因为乍一看,它看起来像一个右值引用。事实证明,T&&也与lvalues绑定:
foo(make_triangle()); // T is unique_ptr<Shape>, T&& is unique_ptr<Shape>&&
unique_ptr<Shape> a(new Triangle);
foo(a); // T is unique_ptr<Shape>&, T&& is unique_ptr<Shape>&
如果自变量是X类型的右值,则T被推断为X,因此T&表示X&&。这是任何人都会想到的。但如果参数是X类型的左值,由于特殊规则,T被推断为X&,因此T&&将意味着类似X&&&的东西。但由于C++仍然没有引用的概念,因此类型X&&&被折叠为X&。这听起来可能会让人困惑和无用,但引用折叠对于完美转发至关重要(这里将不讨论)。
T&&不是右值引用,而是转发引用。它还与左值绑定,在这种情况下,T和T&&都是左值引用。
如果要将函数模板约束为右值,可以将SFINAE与类型特征结合起来:
#include <type_traits>
template<typename T>
typename std::enable_if<std::is_rvalue_reference<T&&>::value, void>::type
foo(T&&);
移动的实施
现在您了解了引用折叠,下面是std::move的实现方式:
template<typename T>
typename std::remove_reference<T>::type&&
move(T&& t)
{
return static_cast<typename std::remove_reference<T>::type&&>(t);
}
如您所见,move接受任何类型的参数,这要归功于转发引用T&&,并返回一个右值引用。std::remove_reference<T>::type元函数调用是必要的,因为否则,对于X类型的lvalues,返回类型将是X&&&,这将折叠为X&。由于t始终是左值(请记住,命名的右值引用是左值),但我们希望将t绑定到右值引用,因此必须将t显式转换为正确的返回类型。返回右值引用的函数的调用本身就是一个xvalue。现在您知道xvalue的来源了;)
返回右值引用(如std::move)的函数的调用是一个xvalue。
注意,在本例中,通过右值引用返回是很好的,因为t不表示自动对象,而是由调用者传入的对象。
简单(实用)地说:
复制对象意味着复制其“静态”成员,并为其动态对象调用新运算符。正确的
class A
{
int i, *p;
public:
A(const A& a) : i(a.i), p(new int(*a.p)) {}
~A() { delete p; }
};
然而,移动一个对象(我重复,从实际的角度来看)意味着只复制动态对象的指针,而不创建新的指针。
但是,这不危险吗?当然,您可以破坏动态对象两次(分段错误)。因此,为了避免这种情况,您应该使源指针“无效”,以避免破坏它们两次:
class A
{
int i, *p;
public:
// Movement of an object inside a copy constructor.
A(const A& a) : i(a.i), p(a.p)
{
a.p = nullptr; // pointer invalidated.
}
~A() { delete p; }
// Deleting NULL, 0 or nullptr (address 0x0) is safe.
};
好的,但是如果我移动一个对象,源对象就会变得无用,不是吗?当然,但在某些情况下,这非常有用。最明显的一点是,当我用匿名对象(时态、右值对象……,你可以用不同的名称调用它)调用函数时:
void heavyFunction(HeavyType());
在这种情况下,将创建一个匿名对象,然后复制到函数参数,然后删除。因此,这里最好移动对象,因为您不需要匿名对象,而且可以节省时间和内存。
这导致了“右值”引用的概念。它们存在于C++11中,只是为了检测接收到的对象是否是匿名的。我想你已经知道“左值”是一个可赋值的实体(=运算符的左边部分),所以你需要一个对象的命名引用来充当左值。右值正好相反,一个没有命名引用的对象。因此,匿名对象和右值是同义词。因此:
class A
{
int i, *p;
public:
// Copy
A(const A& a) : i(a.i), p(new int(*a.p)) {}
// Movement (&& means "rvalue reference to")
A(A&& a) : i(a.i), p(a.p)
{
a.p = nullptr;
}
~A() { delete p; }
};
在这种情况下,当应该“复制”A类型的对象时,编译器会根据传递的对象是否命名来创建左值引用或右值引用。如果没有,将调用移动构造函数,并且您知道该对象是临时的,您可以移动其动态对象而不是复制它们,从而节省空间和内存。
务必记住,“静态”对象总是被复制的。没有办法“移动”静态对象(堆栈中的对象,而不是堆中的对象)。因此,当对象没有动态成员(直接或间接)时,区分“移动”/“复制”是不相关的。
如果您的对象很复杂,并且析构函数具有其他次要影响,例如调用库的函数、调用其他全局函数或其他任何函数,那么最好使用标志来表示移动:
class Heavy
{
bool b_moved;
// staff
public:
A(const A& a) { /* definition */ }
A(A&& a) : // initialization list
{
a.b_moved = true;
}
~A() { if (!b_moved) /* destruct object */ }
};
因此,代码更短(不需要为每个动态成员分配nullptr),更通用。
其他典型问题:A&&和常量A&&之间的区别是什么?当然,在第一种情况下,你可以修改对象,在第二种情况下不是,而是,实际意义?在第二种情况下,您不能修改它,因此您没有办法使对象无效(除了使用可变标志或类似的标志),并且与复制构造函数没有实际区别。
什么是完美的转发?重要的是要知道,“右值引用”是对“调用者范围”中命名对象的引用。但在实际作用域中,右值引用是对象的名称,因此它充当命名对象。如果将右值引用传递给另一个函数,则传递的是一个命名对象,因此该对象不像时间对象那样被接收。
void some_function(A&& a)
{
other_function(a);
}
对象a将被复制到other_function的实际参数。如果希望对象a继续被视为临时对象,则应使用std::move函数:
other_function(std::move(a));
使用这一行,std::move将将a强制转换为右值,other_function将接收该对象作为未命名对象。当然,如果other_function没有特定的重载来处理未命名对象,那么这种区别并不重要。
这是完美的转发吗?不是,但我们很接近。完美转发仅在使用模板时有用,其目的是:如果我需要将一个对象传递给另一个函数,我需要如果我接收到一个命名对象,该对象将作为一个命名的对象传递,如果没有,我希望像未命名的对象一样传递它:
template<typename T>
void some_function(T&& a)
{
other_function(std::forward<T>(a));
}
这是一个使用完美转发的原型函数的签名,在C++11中通过std::forward实现。此函数利用模板实例化的一些规则:
`A& && == A&`
`A&& && == A&&`
因此,如果T是对a的左值引用(T=a&),那么a也是(a&&&=>a&)。如果T是对a的右值引用,则也是(a&&&&&=>a&&)。在这两种情况下,a都是实际作用域中的命名对象,但从调用方作用域的角度来看,T包含其“引用类型”的信息。此信息(T)作为模板参数传递给转发,并根据T的类型移动或不移动“a”。
推荐文章
- cplusplus.com给出的错误、误解或坏建议是什么?
- 找出质数最快的算法是什么?
- c++枚举类可以有方法吗?
- 格式化IO函数(*printf / *scanf)中的转换说明符%i和%d之间的区别是什么?
- 将析构函数设为私有有什么用?
- main()中的Return语句vs exit()
- 为什么c#不提供c++风格的'friend'关键字?
- 在函数的签名中添加关键字
- 我如何在Visual Studio中预处理后看到C/ c++源文件?
- 为什么在标准容器中使用std::auto_ptr<>是错误的?
- 用比较double和0
- 保护可执行文件不受逆向工程的影响?
- 在c++中字符串前面的“L”是什么意思?
- 为什么std::map被实现为红黑树?
- 空括号的默认构造函数