右值引用

所谓的右值引用就是必须将引用绑定到右值的引用,我们通过&&来绑定到右值而不是&, 右值引用只能绑定到即将销毁的对象。右值引用也是引用,因此右值引用也只不过是对象的别名而已。右值引用可以绑定到要求转换的表达式、字面常量或者返回右值的表达式,但是右值不能绑定到一个左值上。


int i = 42;
int &r = i; // 正确,r引用i
int&& rr = i; // 错误,不能将右值引用绑定到左值
int &r2 = i * 42; // 错误,i*42是一个右值
const int& r3 = i * 42;
int&& rr2 = i * 42;

返回左值的函数、连同赋值、下标、解引用和前置递增或递减运算符都是返回左值的例子。
返回非引用类型的函数、连同算数、关系、位以及后置递增或递减运算符,都是生成右值的例子。

我们可以将一个const的左值引用或一个右值引用绑定到生成右值的例子上。


我们不能把右值直接绑定到一个变量上,即使这个变量时右值引用类型也不行。

int&& rr1 = 42;
int&& rr2 = rr1; // 错误,表达式rr1是左值


标准库move函数
我们可以通过标准库函数move来获得绑定到左值上的右值引用。

int&& rr3 = std::move(rr1);

move旨在告诉编译器,我们希望把一个左值像右值一样使用它。使用move函数就意味着:除了对rr1赋值或销毁之外,我们不能再使用它。使用move之后,我们就不能对源对象的值做任何假设。




移动构造函数和移动赋值运算符

移动构造函数和移动赋值运算符移动对象资源,它们从对象中窃取资源,而不是像拷贝控制成员一样从对象拷贝资源。
我们为动态内存管理类添加移动构造函数:

StrVec::StrVec(StrVec&& s) noexcepd : first(s.first), last_end(s.last_end), cap(s.cap){
s.first = s.last_end = s.cap = nullptr; // 将资源的源对象进入安全状态,对其运行析构函数是安全的。
}

移动构造函数类似拷贝构造函数,第一个形参必须是该类类型的一个引用,而其他额外的参数必须有默认实参。

移动构造函数必须保证移动后源对象处于一个安全的状态:析构它是无害的。 移动后,这些资源不再属于源对象。

移动构造函数在进行移动时,因为并没有分配任何资源,所以并不会抛出任何异常。当写一个不会抛出异常的程序时, 我们应该通知标准库。除非标准库知道我们的移动构造函数不会抛出异常,否则它会认为我们的程序可能会抛出异常。
为了通知标准库我们的移动构造函数不会抛出异常,我们的方法是在我们的移动构造函数中指明noexcept。它放在函数的参数列表和初始化列表开始的冒号之间:

class StrVec {

public:
StrVec(StrVec&&) noexcept;
/*
*/
};
StrVec::StrVec(StrVec&& s) noexcepd : first(s.first), last_end(s.last_end), cap(s.cap){
s.first = s.last_end = s.cap = nullptr; // 将资源的源对象进入安全状态,对其运行析构函数是安全的。
}

类似const声明,我们需要在移动构造函数的声明和定义处都指定noexcept。

移动赋值运算符

我们为StrVec添加移动赋值运算符:
StrVec& StrVec::operator = (StrVec&& rhs) noexcept {

	if (this != &rhs) {

		free();
first = rhs.first;
last_end = rhs.last_end ;
cap = rhs.cap;
rhs.first = rhs.last_end = rhs.cap = nullptr;
}
return *this;
}

移动后源对象必须可析构,我们不能对移动后的源对象做任何假设。


合成的移动操作


如果一个类定义了自己的拷贝构造函数、拷贝赋值运算符和析构函数,那么编译器就不会为我们合成默认的移动操作。如果一个类没有对应的移动操作,则类使用拷贝操作来代替移动操作。

如果一个类没有定义任何拷贝操作,且类的每个非static成员都可以移动,编译器才会为它合成移动构造函数或移动赋值运算符。

移动操作不想拷贝操作,永远不会隐式的定义为删除的函数。如果我们显示的要求编译器生成=default的移动操作,且编译器不能移动所有成员,那么移动操作就会被定义为删除的。


  • 与拷贝构造函数不同,移动构造函数被定义为删除的函数的条件是:有类成员定义了自己的拷贝构造函数且未定义移动构造函数,或者是有类成员未定义自己的拷贝构造函数但是编译器不能为其合成移动构造函数。移动赋值运算符的情况类似。
  • 如果有类成员的移动构造函数或移动赋值运算符被定义为删除的或不可访问的(例如private),则类的移动构造函数或移动赋值运算符被定义为删除的。
  • 类似拷贝构造函数,如果类的析构函数被定义为删除的或不可访问的, 则类的移动构造函数被定义为删除的。
  • 类似拷贝赋值运算符,如果有类成员是const或是引用,则类的移动赋值运算符被定义为删除的。
移动操作和合成的拷贝控制成员有一个相互作用的关系:如果一个类定义了自己的移动构造函数或移动赋值运算符,则该类的合成拷贝构造函数或拷贝赋值运算符被定义为删除的。



如果一个类没有定义移动构造函数,函数匹配规则保证该类型的对象会被拷贝。用拷贝构造函数代替移动构造函数几乎肯定是安全的。

所有五个拷贝控制成员应该看做一个整体,一般来说,如果一个类定义了任何一个拷贝操作,它就应该定义所有的五个操作。


移动迭代器


新标准库定义了一个移动迭代器 make_move_iterator, 它接受一个迭代器参数,将一个普通迭代器转换为一个移动迭代器。普通迭代器解引用得到的是左值,而移动迭代器解引用得到的是一个右值。


右值引用和成员函数

一个成员函数能同时提供拷贝版本和移动版本,它能从中受益。

这种允许移动的成员函数通常使用拷贝/移动构造函数和赋值运算符相同的参数模式:一个指向const的左值引用,一个指向非const的右值引用。

例如,定义了push_back的标准库有两个版本:
void push_back(const X&);
void push_back(X&&);

我们就可以为动态内存管理类定义另一个版本的push_back:

class StrVec {

public:
void push_back(const std::string&); // 拷贝元素
void push_back(std::string&&); // 移动元素
// 其他定义
}; void StrVec::push_back(const std::string& s) {
chk_n_alloc();
alloc.construct(last_end++, s);
} void StrVec::push_back(std::string&& s) {
chk_n_alloc();
alloc.construct(last_end++, std::move(s));
}

右值和左值引用函数



我们通常在对象上调用成员函数,不管这个对象是左值还是右值:
string s1 = "a value", s2 = "another";
auto n = (s1 + s2).find('a');

在此例中,我们在string的右值上调用成员函数,而还有更令我们惊奇的操作:

s1 + s2 = "wow";

我们对一个右值进行了赋值。


在旧标准中存在这种使用方式,而新标准为了兼容旧标准,仍然保留了这种使用方式。
但我们可以在自己的类中阻止这种方式,我们希望强制左侧运算对象是一个左值。
我们指出this的左值/右值性质和const成员函数相同,在参数列表后面放置一个引用限定符

class Foo {

public:
Foo& operator = (const Foo&) &; // 只能向可修改的左值赋值
}; Foo& Foo::operator = (const Foo& rhs)& {
// 赋值操作
return *this;
}

引用限定符可以&或&&, 分别指出this可以指向一个左值或右值,类似const限定符,引用限定符只能用于非static函数,而且必须同时出现在声明和定义中。

对于限定 &的函数,我们只能将它用于左值,限定&&的函数,只能用于右值。

可以同时用const和引用限定,不过引用限定符要跟在const之后。

class Foo {

public:
Foo someMem() & const;// 错误,const必须在引用限定符之前
Foo anotherMem() const &; // 正确
};


重载和引用函数

当我们定义const成员函数时 ,可以定义两个版本,唯一的差别是一个有const而另一个没有。但是我们定义引用限定函数的重载版本时,则不一样,如果我们定义两个或两个以上具有相同名字和相同参数列表的成员函数,必须给所有的重载函数加上引用限定符,或者所有都不加。



class Foo {

public:
Foo someMem() &;
Foo anotherMem() const; // 错误,必须加上引用限定符
};


C++ Primer : 第十三章 : 拷贝控制之对象移动的更多相关文章

  1. C++ Primer : 第十三章 : 拷贝控制之拷贝控制和资源管理

    定义行为像值的类 行为像值的类,例如标准库容器和std::string这样的类一样,类似这样的类我们可以简单的实现一个这样的类HasPtr. 在实现之前,我们需要: 定义一个拷贝构造函数,完成stri ...

  2. C++ Primer : 第十三章 : 拷贝控制之拷贝、赋值与销毁

    拷贝构造函数 一个构造函数的第一个参数是自身类类型的引用,额外的参数(如果有)都有默认值,那么这个构造函数是拷贝构造函数.拷贝构造函数的第一个参数必须是一个引用类型. 合成的拷贝构造函数   在我们没 ...

  3. C++ Primer : 第十三章 : 拷贝控制示例

    /* Message.h */ #ifndef _MESSAGE_H_ #define _MESSAGE_H_ #include <iostream> #include <strin ...

  4. [C++ Primer] : 第13章: 拷贝控制

    拷贝, 赋值与销毁 当定义一个类时, 我们显示地或隐式地指定在此类型的对象拷贝, 移动, 赋值和销毁时做什么. 一个类通过定义5种特殊的成员函数来控制这些操作, 包括: 拷贝构造函数, 拷贝赋值运算符 ...

  5. 【C++ Primer 第十三章】4. 拷贝控制示例

    拷贝控制示例 #include<iostream> #include<string> #include<set> #include<vector> us ...

  6. 《C++ Primer》笔记 第13章 拷贝控制

    拷贝和移动构造函数定义了当用同类型的另一个对象初始化本对象时做什么.拷贝和移动赋值运算符定义了将一个对象赋予同类型的另一个对象时做什么.析构函数定义了当此类型对象销毁时做什么.我们称这些操作为拷贝控制 ...

  7. C++Primer 第十三章

    //1.当定义一个类时,我们显示地或隐式地指出在此类型的对象(注意这里是此类型的对象,而不包括此类型的指针)拷贝,移动,赋值,销毁时做什么.一个类通过定义五种特殊的成员函数来控制这些操作:拷贝构造函数 ...

  8. 【c++ Prime 学习笔记】第13章 拷贝控制

    定义一个类时,可显式或隐式的指定在此类型对象上拷贝.移动.赋值.销毁时做什么.通过5种成员函数实现拷贝控制操作: 拷贝构造函数:用同类型的另一个对象初始化本对象时做什么(拷贝初始化) 拷贝赋值算符:将 ...

  9. C++ Primer 5th 第13章 拷贝控制

    当一个对象的引用或者指针离开作用域时,析构函数不会执行. 构造函数有初始化部分(初始化列表)和函数体. 析构函数有析构部分和函数,但析构函数的析构部分是隐式的.

随机推荐

  1. wampserver You don't have permission to access / on this server. 解决 方法(转,正好碰到这样的事情了就转下来)

    最近在安装最近版wampserver 2.2 d时发现安装好后启动服务器,访问localhost显示You don't have permission to access / on this serv ...

  2. 使用 Jasmine 进行测试驱动的 JavaScript 开发

    Jasmine 为 JavaScript 提供了 TDD (测试驱动开发)的框架,对于前端软件开发提供了良好的质量保证,这里对 Jasmine 的配置和使用做一个说明. 目前,Jasmine 的最新版 ...

  3. Delphi名站以及高手Blog

    以前知道的: http://cnblogs.com/del (万一兄的,这个不用解释了) http://www.cnblogs.com/del/archive/2010/04/25/1720750.h ...

  4. git diff patch

    如何生成patch:修改一个地方,然后git diff > xxx.patch 就会生成一个patch文件,这里的关键似乎是, 源文件的某个模块的版本要和线上发布的最新版本要一致,这样patch ...

  5. laravel Ajax post方式的使用

    以jquery ajax 的post的方式为例 验证邮箱输入格式是否正确 html <div class="fl"> <input type="emai ...

  6. 【转】 深入main函数中的参数argc,argv的使用详解

    C/C++语言中的main函数,经常带有参数argc,argv,如下: 复制代码 代码如下: int main(int argc, char** argv) 这两个参数的作用是什么呢?argc 是指命 ...

  7. SpringMVC -- 梗概--壹

    1.springMVC:MVC开源框架 2.springMVC开发流程: 2.1 导包: 2.2 配置前端控制器(核心) DispatcherServlet <servlet> <s ...

  8. oracle sql

    show user desc 'table' SELECT DISTINCT SELECT * FROM emp WHERE comm is NOT NULL; SELECT * FROM emp W ...

  9. 如何做出header,footer固定定位后让main主体部分可以滑动,在微信浏览器中滑动到最后不出现黑边的情况

    <!doctype html>   <html>   <head>   <meta charset="utf-8">   </ ...

  10. [转]JAVA虚拟机的生命周期

    JAVA虚拟机体系结构 JAVA虚拟机的生命周期 一个运行时的Java虚拟机实例的天职是:负责运行一个java程序.当启动一个Java程序时,一个虚拟机实例也就诞生了.当该程序关闭退出,这个虚拟机实例 ...