在介绍题目中内容之前我们先看一个几个示例
作为一种追求执行效率的语言, C++在用临时对象或函数返回值给左值对象赋值时的深度拷贝(deep copy)一直受到诟病。 考虑到临时对象的生命期仅在表达式中持续,如果把临时对象的内容直接移动(move)给被赋值的左值对象,效率改善将是显著的。这就是移动语义的来源。
#include
#include
using namespace std;
struct Base
{
Base():d(new int(0)){}
~Base(){delete d;}
int *d;
};
int main()
{
Base a;
Base b(a);
cout<<*a.d<
g++ dbFreeTest.c -std=c++11 -o dbfree
后
运行会出先如下错误
0
析构
0
析构
free(): double free detected in tcache 2
Aborted
分析原因为:b(a)会调用合成的拷贝构造函数,而此处调用后是浅拷贝,只是使得b的d指针指向了a的d指针所指向的内存,并没拷贝数据;而释放时先释放掉a,a的d所指向的内存释放掉,此时b的d就成了悬挂指针,指向的内存已不复存在,再次释放就会导致double free
针对示例1,我们可以自定义拷贝构造函数
改造如下
struct Base
{
Base():d(new int(0)){}
Base(Base & h):d(new int(*h.d)){}//拷贝构造函数,从堆中分配内存,并用*h.d进行初始化
~Base(){delete d;}
int *d;
};
运行后不存在两次释放的问题了
0
0
析构
析构
那么a.d所指的内存很大的时,这样拷贝的效率将会变的非常低
#include
#include
using namespace std;
struct Base
{
Base():d(new int(0)){}
Base(const Base&)=delete;
Base(Base&& h)noexcept:d(h.d)
{
h.d = nullptr;
cout << "Move constructor: dynamic array is moved!\n";
}
~Base(){cout<<"析构"<
运行结果:
0
Move constructor: dynamic array is moved!
0
析构
析构
这样我们就可以在构造b时不分配内存构造了。
与传统的拷贝赋值运算符(copy assignment)成员函数、拷贝构造(copy ctor)成员函数对应,移动语义需要有移动赋值(move assignment)成员函数、移动构造(move ctor)成员函数的实现机制。可以通过函数重载来确定是调用拷贝语义还是移动语义的实现。
再看下述两个交换的例子,来理解下移动语义的好处:
template
void swap(T& a, T& b)
{
T tmp(a);
a = b;
b = tmp;
}
X a, b;
swap(a, b);
template
void swap(T& a, T& b)
{
T tmp(std::move(a));
a = std::move(b);
b = std::move(tmp);
}
X a, b;
swap(a, b);
尽可能使用 std::move,如图所示 swap上面的函数,给了我们 以下重要好处:
下文出处
C++11标准引入了右值引用数据类型与移动语义,因而左值与右值的定义发生了很大变化。右值引用变量绑定到右值上,延长了右值对应的临时对象的生存期。移动语义把临时对象的内容移动(move)到左值对象上。
因而在C++11,对于值的分类,要考虑标识(identity)与可移动性(movability),二者的组合产生了五种分类:
lvalue
可以用取地址运算符&获取地址的表达式。也可定义为非临时对象或非成员函数。具有标识,但不可移动。这也是C++03的经典左值。可用于初始化左值引用。可以有不完备类型(incomplete type)。包括:
void foo(X&& x)
{
X anotherX = x; // 调用 X(X const & rhs)拷贝构造,因为这是x一个具名的右值引用,所以是左值
}
X&& goo();
X x = goo(); //调用 X(X&& rhs)移动的拷贝构造 ,以为这是个不具名的右值引用,故为右值
xvalue(expiring value
)具有标识,并且可以移动。对应的对象接近生存期结束,但其内容尚未被移走。可以多态;非类对象可以cv限定。包括:
prvalue
不具有标识,但可以移动。对应临时对象或不对应任何对象的值。纯右值不能是多态的;临时对象的动态类型是表达式类型;非类且非数组的纯右值不能是const限定的;不能有不完备类型(除了void)。包括:
glvalue
具有标识。包括左值与临终值。可以多态、动态类型。
rvalue
可以移动。包括濒死值与纯右值。不能通过&运算符取地址。
C++的非静态成员函数调用表达式(obj.func与ptr->func),非静态成员函数指针调用表达式(obj.*mfp与ptr->*mfp)被当作纯右值,但是不能用于初始化引用,不能做函数实参,仅仅能用作函数调用表达式左边的操作数,如(pobj->*ptr)(args)。
返回void的函数调用表达式、到void的类型转换表达式、throw表达式被当作纯右值。但是不能用于初始化引用,不能做函数实参。可用于某些上下文环境中(如单独作为一行语句、逗号操作符的左端表达式等),或返回void的函数的return语句中。此外,throw表达式可用作三元条件操作符的第二或第三操作数。
位元栏(bit field)表达式是左值,但不能用&运算符取地址,不能绑定到非常量左值引用。常量左值引用可以用位域左值初始化,但实际上是另行分配绑定了一个对象。
示例1:
int a = 42;
int b = 43;
// a和b都是左值lvalues:
a = b; // ok
b = a; // ok
a = a * b; // ok
// a * b 是一个右值 rvalue:
int c = a * b; // ok, 右值rvalue在表达式的右侧
a * b = 42; // error, 右值rvalue在表达式左侧
int var = 0;
var = 1 + 2; // ok, var在这是左值
var + 1 = 2 + 3; // error, var + 1 是右值
int* p1 = &var; // ok, var是左值可取址
int* p2 = &(var + 1); // error,var + 1 是右值
UserType().member_function(); // ok, calling a member function of the class rvalue
示例2:
// 左值lvalues:
//
int i = 42;
i = 43; // ok, i是一个左值lvalue
int* p = &i; // ok, i 是一个左值 lvalue
int& foo();
foo() = 42; // ok, foo()是一个左值 lvalue
int* p1 = &foo(); // ok, foo() 是一个左值 lvalue
// 右值rvalues:
//
int foobar();
int j = 0;
j = foobar(); // ok, foobar() 是一个右值rvalue
int* p2 = &foobar(); // error, 不能对一个右值进行取址
j = 42; // ok, 42是一个右值rvalue
示例3
下标操作符是这种形式的函数 T& operator[](T*, ptrdiff_t)
,所以A[0]
是一个左值 ,其中A 是数组的类型。
解引用操作符是这种形式的函数T& operator*(T*)
,因此*p
是一个左值, 其中 p 是一个指针类型
运算符就是这种形式
减运算符 T operator-(T)
,所以 -x 是一个右值 rvalue.
示例4
#include
#include
int i = 101, j = 101;
int foo(){ return i; }
int&& bar(){ return std::move(i); }
void set(int&& k)
{ //不会发生折叠
k = 102; //K 是个左值,具名的右值引用被当作左值
}
int main()
{
foo();
std::cout << i << std::endl;//101
set(bar());
std::cout << i << std::endl;//102
}
图片连接
大致需要知道的是我们使用的* 、&& 、&、[]其实都是声明符中的一种
知道了值和声明符,那么就开始进行引用的说明吧。
在此之前再插入一条灵魂疑问:引用为何必须初始化?
如果引用未初始化,则无法对其进行初始化,因为任何分配给引用的尝试总是分配给它的所指对象。
int& numberRef; // 假装这是允许的
numberRef = number; // 将数字复制到某个随机的存储位置
参考1:[A Brief Introduction to Rvalue References]
好了,接下来看下C++11引用绑定规则:
注意事项:
1.引用必须被初始化为指代一个有效的对象或函数
2. 引用一旦初始化,便无法引用另一对象。
3. 因为引用不是对象,所以不存在引用的数组,不存在指向引用的指针,不存在引用的引用
int& a[3]; // 错误
int&* p; // 错误
int& &r; // 错误
std::string str = "Test";
std::string const &r3 = s;//OK:底层const
const std::string &r4 = s;//OK:底层const
std::string & const r5 = s;//错误,
//下面这两个均错误,无法修改指向的值
r3 +="apple"; r4 +="apple";
左值引用 (l-ref, lvalue reference) 用 & 符号引用 左值(但不能引用右值)。可拥有不同的 cv 限定。
当函数的返回值是左值引用时,函数调用表达式变成左值表达式
#include
#include
char& char_number(std::string& s, std::size_t n) {
return s.at(n); // string::at() 返回 char 的引用
}
int main() {
std::string str = "Test";
char_number(str, 1) = 'a'; // 函数调用是左值,可被赋值
std::cout << str << '\n';
}
示例:
struct A {};
struct B : A {} b;
A& ra = b; // ra 引用 b 中的 A 子对象
const A& rca = b; // rca 引用 b 中的 A 子对象
右值引用(rvalue reference),是C++程序设计语言自C++11标准提出的一类数据类型。用于实现移动语义(move semantic)与完美转发(perfect forwarding)
无论是传统的左值引用还是C++11引进的右值引用,从编译后的反汇编层面上,都是对象的存储地址与自动解引用(dereference)。因此,右值引用与左值引用的变量都不能悬空(dangling),也即定义时必须初始化从而绑定到一个对象上。
设X为任何一种数据类型,则定义X&&是到数据类型X的右值引用(rvalue reference to X)。传统的引用类型X&被称为左值引用(lvalue reference to X)。
例如:
int i;
int &j=i; //定义传统的左值引用并初始化
int &&k=std::move(i); //定义一个右值引用并初始化。std::move在中
通过模板或 typedef 中的类型操作可以构成引用的引用,此时适用引用折叠(reference collapsing)规则:右值引用的右值引用折叠成右值引用,所有其他组合均折叠成左值引用。
注:decltype也可能会用到引用塌缩规则
对于一个类型X:
typedef int& lref;
typedef int&& rref;
int n;
//函数模板
templatevoid foo(T&&);
//typedef
lref& r1 = n; // r1 的类型是 int&
lref&& r2 = n; // r2 的类型是 int&
rref& r3 = n; // r3 的类型是 int&
rref&& r4 = 1; // r4 的类型是 int&&
//decltype
int var;
decltype(var)&& v1=std::move(var); //类型是int&&
== 注:引用折叠只能应用于间接创建的引用的引用,如类型别名或者模板参数 ==
转发引用是一种特殊的引用,它保持函数实参的值类别,使得 std::forward 能用来转发实参。转发引用是下列之一:
//示例1
template
int f(T&& x) { // x 是转发引用
return g(std::forward(x)); // 从而能被转发
}
int main() {
int i;
f(i); // 实参是左值,调用 f(int&), std::forward(x) 是左值
f(0); // 实参是右值,调用 f(int&&), std::forward(x) 是右值
}
//示例2
template
int g(const T&& x); // x 不是转发引用:const T 不是无 cv 限定的
//示例3
template struct A {
template
A(T&& x, U&& y, int* p); // x 不是转发引用:T 不是构造函数的类型模板形参
// 但 y 是转发引用
};
//示例4
template class vector {
public:
void push_back(T&& x); // T是类模板参数 ⇒ 该成员函数不需要类型推导;这里的函数参数类型就是T的右值引用
template void emplace_back(Args&& args); // 该成员函数是个函数模板,有自己的模板参数,需要类型推导
};
//示例5
templatevoid f(const T&& param); // 这里的“&&”不需要类型推导,意味着“常量类型T的右值引用”
templatevoid f(std::vector&& param); // 这里的“&&”不需要类型推导,意味着std::vector的右值引用
auto&& vec = foo(); // foo() 可以是左值或右值,vec 是转发引用
auto i = std::begin(vec); // 也可以
(*i)++; // 也可以
g(std::forward(vec)); // 转发,保持值类别
for (auto&& x: f()) {
// x 是转发引用;这是使用范围 for 循环最安全的方式
}
auto&& z = {1, 2, 3}; // *不是*转发引用(初始化器列表的特殊情形)
Type1&& var1=anotherType1Instance; // var1的类型是右值引用,但是作为左值
auto&& var2=var1; //var2的类型是左值引用
std::vector v;
auto&& val = v[0]; // std::vector::operator[]的返回值是元素左值,所以val的类型是左值引用
Widget makeWidget(); // 类工厂函数
Widget&& var1 = makeWidget() // var1的类型是右值引用,具有左值。
Widget var2 = static_cast(var1); // var2在初始化时可以使用移动构造函数。
尽管引用一旦初始化就始终指代一个有效的对象或函数,但有可能创建一个程序,其中被指代对象的生存期结束而引用仍保持可访问(悬垂(dangling))
std::string& f()
{
std::string s = "Example";
return s; // 退出 s 的作用域:调用其析构函数并解分配其存储
}
std::string& r = f(); // 悬垂引用
std::cout << r; // 未定义行为:从悬垂引用读取
std::string s = f(); // 未定义行为:从悬垂引用复制初始化
== 逐步理解该节阐述与【引子1】内容相似,但是是一种不同的角度看待问题。==
假设X是一个类,它持有指向某些资源的指针或句柄,例如m_pResource
。
X& X::operator=(X const & rhs)
{
// [...]
//对rhs.m_pResource所引用的内容做一个拷贝
// 销毁m_pResource引用的资源。
// 将克隆绑定到m_pResource
// [...]
}
X foo();
X x;
x = foo();
x=foo()
需要做的操作如下:
很明显,在x和临时函数之间交换资源指针(句柄),然后让临时函数的析构函数析构x的原始资源,这样做是可以的,而且效率更高。换句话说,在这种特殊情况下右边的赋值是一个右值,我们希望赋值复制操作是这样的。
c++11可以通过重载赋值运算符实现:
X& X::operator=(<一个神秘的我了个去类型> rhs)
{
// [...]
// 将 this->m_pResource 和rhs.m_pResource交换
// [...]
}
由于定义了赋值操作符的重载,所以“一个神秘的我了个去类型”本质上必须是引用:我们当然希望右手边的值通过引用传递给我们。此外,我们预计“一个神秘的我了个去类型”会有以下行为:当需要在两个重载中进行选择,其中一个是普通引用,另一个是“一个神秘的我了个去类型”时,右值必须选择“一个神秘的我了个去类型”,而左值必须选择普通引用。
如下:
void foo(X& x); // 重载左值引用
void foo(X&& x); // 重载右值引用
X x;
X foobar();
foo(x); // 参数是左值: 调用 foo(X&)
foo(foobar()); // 参数是右值: 调用 foo(X&&)
以上便是我们写了这么多想要的移动语义的内容了。那么移动语义的定义具体是什么呢?
查阅了很多资料,其实对于移动语义并未给出非常明确通用的定义。个人认为下面作者对移动语义理解挺好。所以直接截取了其中内容。
链接
总结下来的定义应该为:将内存的所有权从一个对象转移到另外一个对象,高效的移动用来替换效率低下的复制。
至于很多人会用移动语义(std::move())这样的题目来说明移动语义,个人觉得有点误人子弟的意思。std::move()只能说是移动语义的一个很好的标准库例子。
为了让我们自己的类型支持移动操作,需要为其定义移动构造函数和移动赋值运算符。
移动构造函数和移动赋值运算符、拷贝构造函数和赋值运算符,这两个东西天生犯冲;如果自定义了前者后者默认是删除的,如果自定义了后者前者默认删除;所以如果自定义时最好都定义上。
关于 第4条的说明如下
vector
保证,如果我们调用push_back
时发生异常,vector
保证自身不变。
1] 对于push_back
的调用可能会导致重新分配内存,如果在重新分配的过程使用了移动构造函数,且移动了部分而不是全部后抛出了异常,那么旧空间中的移动源已经改变,而新空间未构造的元素尚不存在,此种情况vector无法保证自身不变。
2] vector如果使用拷贝构造函数且发生异常,可以保证旧的vector不变。
3] 为了避免1]中情况出现,除非vector知道元素类型的移动构造函数不抛异常,否则在重新分配内存的过程中,他就必然使用拷贝构造函数而非移动构造函数。
4] 如果想在vector重新分配内存中使用移动而非拷贝,就需要使用noexcept来标记为不抛异常。
在实际开发中,通常在类中自定义移动构造函数的同时,会再为其自定义一个适当的拷贝构造函数,由此当用户利用右值初始化类对象时,会调用移动构造函数;使用左值(非右值)初始化类对象时,会调用拷贝构造函数。
示例源自
#include
using namespace std;
class demo{
public:
demo():num(new int(0)){
cout<<"construct!"<
//编译器会为X生成合成的移动构造函数
struct X{
int i;//内置类型可以移动
std::string s;//string有自己的移动操作
}
struct hasX{
X mem;//X 有合成的移动操作
}
X x,x2 = std::move(x);// 使用合成的移动构造函数
hasX hx hx2 = std::move(hx);//使用合成的移动构造函数
#include
#include
using namespace std;
class Move {
private:
// 将指针声明为类的数据成员
int* data;
public:
// 构造函数
Move(int d)
{
// 在堆中声明对象
data = new int;
*data = d;
cout << "Constructor is called for "
<< d << endl;
};
// 拷贝构造
Move(const Move& source)
: Move{ *source.data }
{
// 深拷贝复制数据
cout << "Copy Constructor is called -"
<< "Deep copy for "
<< *source.data
<< endl;
}
// 移动构造函数
Move(Move&& source)
: data{ source.data }
{
cout << "Move Constructor for "
<< *source.data << endl;
source.data = nullptr;
}
// 析构
~Move()
{
if (data != nullptr)
// 不为空
cout << "Destructor is called for "
<< *data << endl;
else
// data 为nullptr
cout << "Destructor is called"
<< " for nullptr "
<< endl;
// 释放分配给对象的data成员的内存
delete data;
}
};
int main()
{
// Move 类Vector
vector vec;
// 插入数据
vec.push_back(Move{ 10 });
vec.push_back(Move{ 20 });
return 0;
}
运行结果如下:
Constructor is called for 10
Move Constructor for 10
Destructor is called for nullptr
Constructor is called for 20
Move Constructor for 20
Constructor is called for 10
Copy Constructor is called -Deep copy for 10
Destructor is called for 10
Destructor is called for nullptr
Destructor is called for 10
Destructor is called for 20
以下来源
MemoryBlock& operator=(MemoryBlock&& other)
{
}
if (this != &other)
{
}
//释放现有资源
delete[] _data;
执行第一个过程中的步骤 2 和步骤 3 以将数据成员从源对象转移到要构造的对象:
// 从源对象复制数据指针及其长度。
_data = other._data;
_length = other._length;
// 从源对象释放数据指针,这样析构函数就不会多次释放内存。
other._data = nullptr;
other._length = 0;
return *this;
if (this != &other)
{
//释放当前对象的资源
delete[] buf;
size=0;
// 占用other's 资源
size=other.size;
buf=other.buf;
// 重置otther
other.size=0;
other.buf=nullptr;
}
return *this;
T& T::operator= (T&&)
。假设你写了一个类 Base, 你有 通过重载实现移动语义 Base的复制构造函数 和赋值运算符:
Base(Base const & rhs); // non-move semantics
Base(Base&& rhs); // move semantics
现在你写一个类 Derived源自 Base. 为了确保将移动语义应用于 Base部分 你的 Derived对象,你必须重载 Derived的副本 构造函数和赋值运算符也是如此。 让我们看看复制构造函数。 复制赋值运算符的处理方式类似。 左值的版本是 直截了当:
//左值版本
Derived(Derived const & rhs)
: Base(rhs)
{
// Derived-specific stuff
}
//右值引用版本错误示范
Derived(Derived&& rhs)
: Base(rhs) // 错误: rhs是一个具名的右值引用,他是个左值
{
// do somrthing
}
//右值引用版本正确释放
Derived(Derived&& rhs)
: Base(std::move(rhs)) // 正确, 调用Base(Base&& rhs)
{
// do somrthing
}
任何现代编译器都将对原始函数定义应用返回值优化。
假设X是一个重载赋值构造函数和拷贝构造函数以实现移动语义的类,也就是说具有自定义移动构造函数和移动赋值运算符的类。
X foo()
{
X x;
// 处理
return x;
}
这里如果我们将return x;
修改为return std::move(x);
是否会更好呢?答案是否定的。编译器将直接在foo的返回值处构造X对象,而不是在本地构造一个X然后将其复制出来。很明显,这比move语义更好。
我们不能将一个右值引用直接绑定到一个左值上,但我们可以通过std::move
的新标准库函数来获得绑定到左值上的右值引用,此函数定义在头文件utility
中。
例如int &&t1 = std::move(t0);//ok ``move
调用告诉编译器:我们有一个左值,但我们希望像一个右值一样处理它。但我们必须认识到,调用move
就意味着承诺:除了对t0
赋值和销毁外,我们不再使用它。
template< class T >
typename std::remove_reference::type&& move( T&& t ) noexcept{
return static_cast::type&&>(t) ;
}
move的函数参数T&&
是一个指向模板类型参数的右值引用。通过引用折叠,此参数与任何类型的实参匹配。特别是,我们即可以传递给move一个左值,也可以传递给它一个右值。
接下来从下面的例子让我们深入理解下std::move
string s1("hi"),s2;
s2 = std::move(string("bye!"));///> ok:从一个右值移动数据
s2 = std::move(s1);///>ok:但在赋值之后,s1的值是不确定的
std::move(string("bye!"))
推断如下:
T
的类型为string
;remove_reference
用string
进行实例化remove_reference
的type
成员是string
move
的返回类型是string&&
move
的函数参数t
的类型为string&&
因此,这个调用的实例化move
,即函数 string && move(string &&tt)
,函数体返回static_cast
。t
的类型已经是string &&
,于是类型转化什么也不做。因此,此调用的结果就是它所接受的右值引用。
std::move(s1)
的推断如下:
string &
(string
的引用,而非普通的string
)remove_reference
用string &进行实例化remove_reference
的type
成员是string
move
的返回类型仍是string &&
move
的函数参数t
实例化为string &&
,折叠为string &
.move
,即string && move(string &t)
。这个实例的函数体返回static_cast(t)
,在此情况下,t
的类型为string &
,static_cast
将其转换为string &&
。std::move
其实就是static_cast
的显示转换。因为我们可以用static_cast
显式地将一个左值转换为一个右值引用。
以vector为例:
push_back
从c++11开始支持移动版本
void push_back( const T& value );
void push_back( T&& value );
注:
若 T 的移动构造函数不是 noexcept 且 T 不可复制插入 (CopyInsertable) 到 *this ,则 vector 将使用会抛出的移动构造函数。若它抛出,则抛弃保证且效果未指定。
emplace_back
函数定义如下:
template< class... Args > void emplace_back( Args&&... args );
注:
若抛出异常,则此函数无效果(强异常保证)。 若 T 的移动构造函数非 noexcept 且非可复制插入 (CopyInsertable) 到 *this ,则 vector 将使用抛出的移动构造函数。若它抛出,则保证被舍弃,且效果未指定。
#include
#include
#include
int main()
{
std::vector letters;
letters.push_back("abc");
std::string s = "def";
letters.push_back(std::move(s));///>移动作为参数
std::cout << "vector holds: ";
for (auto&& i : letters) std::cout << std::quoted(i) << ' ';
std::cout << "\nMoved-from string holds " << std::quoted(s) << '\n';
}
运行结果如下:
vector holds: "abc" "def"
Moved-from string holds ""
代码来源
// move example
#include // std::move
#include // std::std::cout
#include // std::vector
#include // std::string
class String
{
friend std::ostream& operator<<(std::ostream& os, const String& str);
public:
String(void);
String(const char* data, int length);
String(const char* data);
String(const String& from);//1 复制构造
String& operator = (const String& from);//2 赋值操作符
~String();//3 析构函数
String(String&& from);//4 移动构造
String& operator = (String&& from);//5 移动赋值操作符
protected:
void clear(void);
void copy(const char* data, size_t length);
private:
char* m_data;
size_t m_length;
int m_id;
static int s_i;
};
int String::s_i = 0;
std::ostream& operator<<(std::ostream& os, const String& str)
{
for (size_t i = 0; i < str.m_length; ++i)
os << str.m_data[i];
return os;
}
String::String(void) :m_data(nullptr), m_length(0), m_id(++s_i)
{
std::cout << "String(" << m_id << ")" << std::endl;
}
String::~String()
{
std::cout << "~String(" << m_id << ")" << std::endl; clear();
}
String::String(const char* data, int _length) :m_data(nullptr), m_length(0), m_id(++s_i)
{
std::cout << "String(const char*,int," << m_id << ")" << std::endl;
copy(data, _length);
}
String::String(const char* data) : m_data(nullptr), m_length(0), m_id(++s_i)
{
std::cout << "String(const char*," << m_id << ")" << std::endl;
copy(data, strlen(data));
}
String::String(const String& from) : m_data(nullptr), m_length(0), m_id(++s_i)
{
std::cout << "String(" << m_id << ", const String& " << from.m_id << " )" << std::endl;
if (from.m_data != m_data)
{
copy(from.m_data, from.m_length);
}
}
String& String::operator=(const String& from)
{
std::cout << "String & String::operator=(const String &," << m_id << ")" << std::endl;
if (&from != this)
{
copy(from.m_data, from.m_length);
}
return *this;
}
String::String(String&& from) :m_data(nullptr), m_length(0), m_id(++s_i)
{
std::cout << "String(" << m_id << ", String&& " << from.m_id << " )" << std::endl;
if (from.m_data != m_data)
{
std::swap(m_data, from.m_data);
std::swap(m_length, from.m_length);
}
}
String& String::operator=(String&& from)
{
std::cout << "String & String::operator=(String &&," << m_id << ")" << std::endl;
if (&from != this)
{
std::swap(this->m_data, from.m_data);//接管资源
std::swap(this->m_length, from.m_length);
}
return *this;
}
void String::clear(void)
{
if (nullptr != m_data)
{
delete[] m_data;
m_data = nullptr;
m_length = 0;
}
}
void String::copy(const char* data, size_t length)
{
std::cout << "clear new copy" << std::endl;
clear();
m_data = new char[length];
for (size_t i = 0; i < length; ++i)
{
m_data[i] = data[i];
}
m_length = length;
}
String& GetLeftValue(void)
{
static String s("Static String");
return s;
}
String GetRightValue(void)
{
String s("Static String");
return s;
}
//左值常见的场景:p471
void TestLeftValue(void)
{
auto& leftValue1 = GetLeftValue();//左值表达式:返回左值引用的函数是左值表达式
String left2;
left2 = (left2 = leftValue1);//左值表达式:赋值表达式返回左值
std::vector arrStr(2);
left2 = (arrStr[0]);//左值表达式:下标返回左值
String arr[2];
left2 = (arr[0]);//左值表达式:下标返回左值
auto p = arr;
left2 = (*p);//左值表达式:接引用返回左值
//++left2, --left2;//左值表达式:下标返回左值(如果用户重载该操作符的话)
arrStr.push_back(left2);//左值表达式:左值变量未超出作用域之前变量名返回左值
}
//右值常见的场景:p471
void TestRightValue(void)
{
String&& rightValue = GetRightValue();//右值表达式:返回非引用类型的函数是右值表达式(右值引用赋值给右值引用,延长右值的生命)
const String& leftValue = GetRightValue();//右值表达式:返回非引用类型的函数是右值表达式,右值引用赋值给左值引用(移动构造)
String leftValue1 = GetRightValue();//右值表达式:返回非引用类型的函数是右值表达式(移动构造)
std::vector arrStr;
arrStr.emplace_back(GetRightValue());//右值表达式:返回非引用类型的函数是右值表达式,右值引用赋值给左值引用(移动构造)
String leftValue2;
leftValue2 = GetRightValue();//右值表达式:返回非引用类型的函数是右值表达式,右值赋给左值(移动赋值)
String leftValue3(rightValue);//!!!!左值表达式:右值引用变量是变量表达式,变量都是左值。因为变量只有超出作用域才释放,是左值很合理(复制构造)!!!!!
arrStr.push_back(rightValue);//!!!!左值表达式:右值引用变量是变量表达式,变量都是左值。因为变量只有超出作用域才释放,是左值很合理(复制构造)!!!!!
{
String leftValue3;
String leftValue4("Hello World!");
leftValue3 = std::move(leftValue4);//使用移动赋值
std::cout << "test swap:" << std::endl;
std::swap(leftValue3, leftValue4);//这里会资源交换,因为标准库std::swap优先尝试移动语义
}
//value1 + value2 //右值表达式:算术运算符返回右值(如果用户重载的话)
//value1 - value2 //右值表达式:算术运算符返回右值(如果用户重载的话)
//value1 * value2 //右值表达式:算术运算符返回右值(如果用户重载的话)
//value1 / value2 //右值表达式:算术运算符返回右值(如果用户重载的话)
//value1 < value2 //右值表达式:关系运算符返回右值(如果用户重载的话)
//value1 > value2 //右值表达式:关系运算符返回右值(如果用户重载的话)
//value1 <= value2 //右值表达式:关系运算符返回右值(如果用户重载的话)
//value1 >= value2 //右值表达式:关系运算符返回右值(如果用户重载的话)
//value1 == value2 //右值表达式:关系运算符返回右值(如果用户重载的话)
//value1 != value2 //右值表达式:关系运算符返回右值(如果用户重载的话)
//value1++ value1-- //右值表达式:后自增自减运算符返回右值(如果用户重载的话)
}
String GetValue(void)
{
String a;
String b;
b = a;
return a;//返回非引用类型的函数总是返回右值引用
}
String GetMoveValue(void)
{
String a;
String b;
b = a;
return std::move(a);//变量表达式总是一个左值p471, String&& move(String&)p608, move返回右值引用p609, 返回非引用类型的函数返回右值p471,右值引用的右值引用折叠为右值引用p609,所以函数返回右值引用
}
void TestMove(void)
{
String value = GetValue();//移动构造:返回非引用类型的函数是右值表达式
String value1 = GetMoveValue();//移动构造:返回非引用类型的函数是右值表达式
String leftValue;
auto value2 = std::move(leftValue);//String&& move(String&)
}
int main()
{
TestLeftValue();
TestRightValue();
TestMove();
std::cin.get();
return 0;
}
shared_ptr有如下几种初始化方式:
std::shared_ptr p3(new int(10));
//调用拷贝构造函数
std::shared_ptr p4(p3);//或者 std::shared_ptr p4 = p3;
//调用移动构造函数
std::shared_ptr p5(std::move(p4)); //或者 std::shared_ptr p5 = std::move(p4);
#include
#include
class Frame {};
int main()
{
std::shared_ptr f1(std::move(new Frame())); // 移动构造函数
std::shared_ptr f2 = std::move(new Frame()); // Error,explicit禁止隐式初始化
std::shared_ptr f3(std::move(f4)); // 移动构造函数
std::shared_ptr f4 = std::move(f3); // 移动构造函数
return 0;
}
不能直接通过值给函数传递一个智能指针,因为通过值传递将导致复制真正的形参。如果要让函数通过值接收一个独占指针,则在调用函数时,必须对真正的形参使用 move() 函数:
//函数使用通过值传递的形参
void fun(unique_ptr uptrParam)
{
cout << *uptrParam << endl;
}
int main()
{
unique_ptr uptr(new int);
*uptr = 10;
fun (move (uptr)); // 在调用中使用 move
}
看如下示例
#include
template
void print(T & t){
std::cout << "Lvalue ref" << std::endl;
}
template
void print(T && t){
std::cout << "Rvalue ref" << std::endl;
}
template
void testForward(T && v){
print(v);//v此时已经是个左值了,永远调用左值版本的print
}
int main(int argc, char * argv[])
{
int x = 1;
testForward(x); //实参为左值
testForward(std::move(x)); //实参为右值
}
运行如下:
Lvalue ref
Lvalue ref
查看文档之前的描述,testForward(T && v)
这里v是具名的,因此是左值,因此不管右值还是左值永远永远是调用左值的版本print。
修改testForward(T && v)
如下
template
void testForward(T && v){
print(std::move(v));
}
运行如下:
Rvalue ref
Rvalue ref
这就是为什么用std::forward
的原由了
template
void testForward(T && v){
print(std::forward(v));
}
运行结果如下
Lvalue ref
Rvalue ref
template< class T >
T&& forward( typename std::remove_reference::type& t ) noexcept;
template< class T >
T&& forward( typename std::remove_reference::type&& t ) noexcept;
注:
注意事项
#include
#include
template
struct C
{
T t_;
template ::value
>::type>
C(U&& u) : t_(std::forward(std::move(u).get())) {}
};
class A
{
int data_;
public:
explicit A(int data = 1)
: data_(data) {}
~A() {data_ = -1;}
void test() const{
if (data_ < 0) std::cout << "A is destructed\n";
else std::cout << "A = " << data_ << '\n';
}
};
class Awrap
{
A& a_;
public:
explicit Awrap(A& a) : a_(a) {}
const A& get() const {return a_;}
A& get() {return a_;}
};
template
void test(C c)
{
c.t_.test();
}
int main()
{
std::list > list;
A a(3);
C c((Awrap(a)));
list.push_back(c);
test(c);
test(list.front());
}