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 笔记——拷贝控制的更多相关文章

  1. 【C++ Primer】拷贝控制

    十三.复制控制 1. 复制构造函数 类中的成员函数都默觉得inline类型.所以即使在类定义体内的函数声明显示定义为inline类型,在进行函数定义时也可以将inline进行省略. // 复制构造函数 ...

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

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

  3. C++ Primer : 第十三章 : 拷贝控制之对象移动

    右值引用 所谓的右值引用就是必须将引用绑定到右值的引用,我们通过&&来绑定到右值而不是&, 右值引用只能绑定到即将销毁的对象.右值引用也是引用,因此右值引用也只不过是对象的别名 ...

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

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

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

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

  6. 【C++ Primer 第13章】2. 拷贝控制和资源管理

    拷贝控制和资源管理 • 类的行为像一个值.意味着它应该有自己的状态,当我们拷贝一个像值得对象时,副本和原对象是完全独立的,改变副本不会对原对象有任何影响. • 行为像指针的类则共享状态.当我们拷贝一个 ...

  7. 【C++ Primer 第13章】1. 拷贝控制、赋值和销毁

    拷贝控制.赋值和销毁 如果一个构造函数的第一个参数是自身类的引用,且额外的参数都有默认值,则此构造函数是拷贝控制函数(拷贝构造函数不应该是explicit的). 如果我们没有为一个类定义拷贝构造函数, ...

  8. 【C++ Primer | 15】构造函数与拷贝控制

    合成拷贝控制与继承 #include <iostream> using namespace std; class Base { public: Base() { cout << ...

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

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

随机推荐

  1. sqlmap 使用笔记

    1.sqlmap -hh 查看详细说明 2.使用google proxychains sqlmap -g " inurl:\".php?id=1\" " 自动发 ...

  2. python3-面向对象进阶(内置方法)

    面向对象进阶: isinstance和issubclass 反射 __setattr__,__getattr,__delattr__ __setitem__,__getitem,__delitem__ ...

  3. qemu中使用9p,支持host和guest中共享目录【转】

    转自:https://blog.csdn.net/ayu_ag/article/details/52956351 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csd ...

  4. linux添加swap分区【转】

    概述 添加交换分区主要是因为安装oracle时碰到交换分区太小时无法安装的情况,这时候就需要添加交换分区了. 操作简介 增加swap分区方法: 1.新建磁盘分区作为swap分区 2.用文件作为swap ...

  5. Keepalived详解(三):Keepalived基础功能应用实例【转】

    Keepalived基础功能应用实例: 1.Keepalived基础HA功能演示: 在默认情况下,Keepalived可以实现对系统死机.网络异常及Keepalived本身进行监控,也就是说当系统出现 ...

  6. 检索每个字符串的子串(python散列表实现)

    import re def get_str(i,num): str_list = re.findall(r'.{{{str_length}}}'.format(str_length=i), num) ...

  7. P1262 间谍网络 (tarjan缩点 水过去)

    题目描述 由于外国间谍的大量渗入,国家安全正处于高度的危机之中.如果A间谍手中掌握着关于B间谍的犯罪证据,则称A可以揭发B.有些间谍收受贿赂,只要给他们一定数量的美元,他们就愿意交出手中掌握的全部情报 ...

  8. BZOJ3224/LOJ104 普通平衡树 pb_ds库自带红黑树

    您需要写一种数据结构,来维护一些数,其中需要提供以下操作:1. 插入x2. 删除x(若有多个相同的数,因只删除一个)3. 查询x的排名(若有多个相同的数,因输出最小的排名)4. 查询排名为x的数5. ...

  9. windows系统yolov3的安装教程(图文)

    记于 2018-05-19 13:21:13 距离开始着手安装yolov3已经过去将近20个小时,当然我并没有装那么久啦,就是大概4,5个小时这么久,网络上教程很少,步骤也千奇百怪,这次成功装好后就想 ...

  10. 002_运维SOP

    一. <1>SOP:运维工作的标准化 <2>回滚:代码回滚 <3>降级:是利用有限资源,保障系统核心功能高可用.有损的架构方法 <4>多活切换:多机房切 ...