C++ Primer 笔记——拷贝控制
1.如果构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。拷贝构造函数的第一个参数必须是引用类型(否则会无限循环的调用拷贝构造函数)。
2.如果没有为一个类定义拷贝构造函数,编译器会为我们定义一个合成拷贝构造函数。与合成默认构造函数不同,即使我们定义了其他的构造函数,编译器也会为我们合成一个拷贝构造函数。
3.合成的拷贝构造函数会将其参数的成员逐个拷贝到正在创建的对象中(除了static成员)。对于类类型的成员,会使用其拷贝构造函数来拷贝,虽然我们不能拷贝一个数组,但会逐元素的拷贝一个数组类型的成员。
4.当使用直接初始化时,我们实际上是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数,当我们使用拷贝初始化时,我们要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换。
5.在拷贝初始化过程中,编译器可以(但不是必须)跳过拷贝/移动构造函数,直接创建对象。但是拷贝/移动构造函数必须是存在且可访问的(例如,不能是private)。
std::string str("test"); // 编译器略过了拷贝构造函数
6.与拷贝构造函数一样,如果类未定义自己的拷贝赋值运算符,编译器会为它合成一个。或者我们可以自己重载。
class test
{
public:
test& operator=(const test& t);
private:
int m_id;
}; test& test::operator=(const test& t)
{
m_id = t.m_id;
return *this; // 返回一个此对象的引用
}
7.析构函数释放对象使用的资源,并销毁对象的非static数据成员。由于析构函数不接受参数,所以不能被重载。在一个析构函数中,首先执行函数体,然后销毁成员。成员按初始化顺序的逆序销毁。内置类型没有析构函数,因此销毁内置类型成员什么也不需要做。
8.与普通指针不同,智能指针是类类型,所以具有析构函数,因此与普通指针不同,智能指针成员在析构阶段会被自动销毁。
9.我们只能对编译器可以合成的默认构造函数或拷贝控制成员使用=default,在类内用=default修饰成员的声明时,合成的函数将隐式地声明为内联的,如果不希望合成的成员是内联的,应该在类外定义使用=default。
10.与=defalut不同的是,我们可以对任何函数指定=delete。而且=dalete必须出现在函数第一次声明的时候。对于析构函数已删除的类型,不能定义该类型的变量或释放指向该类型动态分配对象的指针。
test t; // 错误
test *p = new test(); // 正确
delete p; // 错误
11.如果一个类有数据成员不能默认构造,拷贝,复制或销毁,则对应的成员函数将被定义为删除的,例如成员有引用类型或者无法默认构造的const成员等。
12.当你编写一个赋值运算符时,一个好的模式是先将右侧运算对象拷贝到一个局部临时对象中(因为可能将一个对象赋予它自身而且可能会出现异常)。当拷贝完成后,销毁左侧运算对象的现有成员就是安全的了。
13.对于分配了资源的类,自定义swap可能是一种很重要的优化手段,因为我们可以只交换指针而不是交换整个动态分配的内存。而标准库的swap会进行一次拷贝两次赋值。
class test
{
public:
test(int i) :m_p(new int(i)){}
~test() { if (m_p) delete m_p; m_p = nullptr; } int *m_p;
}; inline void swap(test& t1, test& t2)
{
std::swap(t1.m_p, t2.m_p); // 交换指针,而不是动态分配的int
} test t1();
test t2(); std::cout << *t1.m_p << std::endl; // 输出1
std::cout << *t2.m_p << std::endl; // 输出2 using std::swap;
swap(t1, t2); // 如果存在类型特定的swap版本,则不会调用std::swap std::cout << *t1.m_p << std::endl; // 输出2
std::cout << *t2.m_p << std::endl; // 输出1
14.一般而言,一个左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值。所谓右值引用就是必须绑定到右值得引用,我们不能将一个右值引用绑定到一个左值上。
int i = ;
int &r = i; // 正确
int &&rr = i; // 错误,右值引用不能绑定到一个左值上
int &r1 = i * ; // 错误,i*10是一个右值
const int &r2 = i * ; // 正确,我们可以将一个const引用绑定到一个右值上
int &&rr1 = i * ; // 正确
15.由于右值引用只能绑定到临时对象,我们得知:所引用的对象将要被销毁;该对象没有其他用户。这两个特性意味着使用右值引用的代码可以自由地接管所引用的对象的资源。
16.变量是左值,因此我们不能将一个右值引用直接绑定到一个变量上,即使这个变量是右值引用类型也不行。但是我们可以显示的将一个左值转换为对应的右值引用类型。我们还可以通过调用一个名为move的新标准库函数来获得绑定到左值上的右值引用。
int &&rr = ;
int &&rr1 = rr; // 错误,rr是左值
int &&rr2 = std::move(rr); // 正确
move调用告诉编译器,我们有一个左值,但是我们希望像一个右值一样处理它。我们必须认识到,调用move就意味着承诺:除了对rr赋值或销毁它外,我们将不再使用它。在调用move之后,我们不能对移后源对象的值做任何假设。
17.类似拷贝构造函数。移动构造函数的第一个参数是该类类型的引用,但是是一个右值引用,任何额外的参数都必须有默认实参。除了完成资源移动,移动构造函数还必须确保移后源对象被销毁是无害的。特别的是,一旦资源完成移动,源对象必须不再指向被移动的资源。
class test
{
public:
test(int i) :m_p(new int(i)){}
test(test &&t)
{
if (m_p)
delete m_p; // 先释放自己的资源
m_p = t.m_p; // 指向t的资源
t.m_p = nullptr; // 保证t可以被正常析构
} int *m_p;
}; test t1();
test t2(std::move(t1));
18.只有当一个类没有定义任何自己版本的拷贝控制成员,且类的每个非static数据成员都可以移动时,编译器才会为它合成移动构造函数或移动赋值运算符。与拷贝操作不同,移动操作永远不会隐式的定义为删除的函数,但是,如果我们显示的要求编译器生成=default的移动操作,且编译器不能移动所有成员,则编译器会将移动操作定义为删除的函数。
19.定义了一个移动构造函数或移动赋值运算符的类必须也定义自己的拷贝操作。否则,这些成员默认地被定义为删除的。
20.如果一个类既有移动构造函数,也有拷贝构造函数,编译器使用普通的函数匹配规则来确定使用哪个构造函数:移动右值,拷贝左值。如果没有移动构造函数,则都调用拷贝构造函数,赋值预算符也一样。
21.如果我们为一个类添加一个移动构造函数,它实际上也会获得一个移动赋值运算符。
class test
{
public:
test(int i):m_p(new int(i)) {}
test(test &t) { m_p = t.m_p; } // 因为定义了移动构造函数,这里必须显示定义
test(test &&t)
{
if (m_p)
delete m_p;
std::swap(m_p, t.m_p);
} test& operator=(test t)
{
std::swap(m_p, t.m_p);
return *this;
} int* m_p;
}; test t1();
test t2 = t1; // 调用了拷贝构造函数
test t3 = std::move(t1); // 调用了移动构造函数
22.移动迭代器解引用运算符生成一个右值引用。我们通过调用make_move_iterator将一个普通迭代器转换为一个移动迭代器。
std::vector<std::string> vec = { "","","" };
std::allocator<std::string> alloc;
auto first = alloc.allocate();
auto last = std::uninitialized_copy(std::make_move_iterator(vec.begin()), std::make_move_iterator(vec.end()),first); // 移动构造,vec里现在存的已经是空字串
23.我们可以在参数列表后放置一个引用限定符来阻止向右值赋值。
- 类似const限定符,引用限定符只能用于(非static)成员函数,且必须同时出现在声明和定义中
- 类似const,引用限定符也可以区分重载版本
- 引用限定符必须跟随在const限定符之后
- 如果我们定义两个或两个以上具有相同名字和相同参数列表的成员函数,就必须对所有函数都加上引用限定符,或者所有都不加
std::string str1, str2;
(str1 + str2) = "test"; // 可以向右值赋值,但没有意义 class test
{
public:
test(int i) { m_id = i; } test& operator=(const test& t) & // 只能向可修改的左值赋值
{
m_id = t.m_id;
return *this;
} test add() const & // 如果和const一起用,则const必须在前面
{
test t(*this); // 不能改变this
t.m_id++;
return t;
} test add() && // 本对象为右值
{
m_id++;
return *this;
} public:
int m_id;
}; test t1();
t1.add(); // t1是左值,调用test add() const &
test().add(); // 右值,调用test add() &&
class test
{
public:
void add() && ;
void add() const; // 错误,必须加上引用限定符 void sub();
void sub() const; // 正确,两个版本都没有引用限定符
};
C++ Primer 笔记——拷贝控制的更多相关文章
- 【C++ Primer】拷贝控制
十三.复制控制 1. 复制构造函数 类中的成员函数都默觉得inline类型.所以即使在类定义体内的函数声明显示定义为inline类型,在进行函数定义时也可以将inline进行省略. // 复制构造函数 ...
- 《C++ Primer》笔记 第13章 拷贝控制
拷贝和移动构造函数定义了当用同类型的另一个对象初始化本对象时做什么.拷贝和移动赋值运算符定义了将一个对象赋予同类型的另一个对象时做什么.析构函数定义了当此类型对象销毁时做什么.我们称这些操作为拷贝控制 ...
- C++ Primer : 第十三章 : 拷贝控制之对象移动
右值引用 所谓的右值引用就是必须将引用绑定到右值的引用,我们通过&&来绑定到右值而不是&, 右值引用只能绑定到即将销毁的对象.右值引用也是引用,因此右值引用也只不过是对象的别名 ...
- C++ Primer : 第十三章 : 拷贝控制之拷贝控制和资源管理
定义行为像值的类 行为像值的类,例如标准库容器和std::string这样的类一样,类似这样的类我们可以简单的实现一个这样的类HasPtr. 在实现之前,我们需要: 定义一个拷贝构造函数,完成stri ...
- C++ Primer : 第十三章 : 拷贝控制之拷贝、赋值与销毁
拷贝构造函数 一个构造函数的第一个参数是自身类类型的引用,额外的参数(如果有)都有默认值,那么这个构造函数是拷贝构造函数.拷贝构造函数的第一个参数必须是一个引用类型. 合成的拷贝构造函数 在我们没 ...
- 【C++ Primer 第13章】2. 拷贝控制和资源管理
拷贝控制和资源管理 • 类的行为像一个值.意味着它应该有自己的状态,当我们拷贝一个像值得对象时,副本和原对象是完全独立的,改变副本不会对原对象有任何影响. • 行为像指针的类则共享状态.当我们拷贝一个 ...
- 【C++ Primer 第13章】1. 拷贝控制、赋值和销毁
拷贝控制.赋值和销毁 如果一个构造函数的第一个参数是自身类的引用,且额外的参数都有默认值,则此构造函数是拷贝控制函数(拷贝构造函数不应该是explicit的). 如果我们没有为一个类定义拷贝构造函数, ...
- 【C++ Primer | 15】构造函数与拷贝控制
合成拷贝控制与继承 #include <iostream> using namespace std; class Base { public: Base() { cout << ...
- 【C++ Primer 第十三章】4. 拷贝控制示例
拷贝控制示例 #include<iostream> #include<string> #include<set> #include<vector> us ...
随机推荐
- 阿里云ECS centos7配置tomcat
准备:创建好developer目录和tomcat子目录 1.在 http://tomcat.apache.org/download-80.cgi 下载tomcat,通过Xftp拷贝到tomcat目录 ...
- 关于 tp5.0 阿里云 oss 上传文件操作
tp5.0 结合阿里云oss 上传文件 1.引入 oss 的空间( composer install 跑下第三方拓展包及核心代码包) 备注:本地测试无误,放到线上有问题 应该是移动后的路劲(相对于服 ...
- Android软键盘在清单文件中所有配置含义
android:windowSoftInputMode 活动的主窗口如何与包含屏幕上的软键盘窗口交互.这个属性的设置将会影响两件事情: 1> 软键盘的状态——是否它是隐藏或显示——当活动 ...
- docker部署Javaweb环境数据库连接问题
最近在docker部署了一个Javaweb项目运行的环境,在容器中部署了mysql和Javaweb项目,但是本地可以跑项目,放到容器里面不行. 具体报错内容是不能访问数据库. Could not ge ...
- 负载均衡获得真实源IP的6种方法 【转】
除了X-FORWARD-FOR,负载均衡中获得真实源IP的方法还有很多种, 本文抛砖引玉,主要介绍获得真实源IP的多种方法,而不是具体配置, 负载均衡获得真实IP的方法有很多种,将形成专题文章, 本文 ...
- web前端之性能
1.避免布局抖动 function resizeAllParagraphsToMatchBlockWidth() { // Puts the browser into a read-write-rea ...
- Webpack2 中的 NamedModulesPlugin 与 HashedModuleIdsPlugin
要讨论Webpack 2中新增的这两个plugin的功能,还要先从使用Webpack打包的项目的前端资源缓存方案说起. 通常在使用了Webpack的项目中我们会使用CommonsChunkPlugin ...
- mongodb管理与安全认证
mongodb数据管理 数据的导出.数据导入数据导出 mongoexport [使用mongoexport -h查看参数] 数据导入 mongoimport [使用mongoimport -h查看参数 ...
- js判断空字符串、null、undefined、空格、中文空格
代码 function isEmpty(obj) { if (obj === null) return true; if (typeof obj === 'undefined') { return t ...
- linux系统快捷键
tab 补全命令 两次tab 列出所有以字符前缀开头的命令 ctrl A 把光标移到命令行开头 ctrl E 把光标移到命令行结尾 ctrl C 强制终止当前的命令 ct ...