前言:当定义一个类的时候,我们显示或者隐式地指定在此类型的对象拷贝,移动,赋值,销毁时做些什么,一个类通过定义五种特殊的成员函数来控制这些操作,包括拷贝构造函数,拷贝赋值运算符,移动构造函数,移动赋值运算符和析构函数, 拷贝和移动构造函数定义了同类型的另一个对象初始化本对象时做什么,拷贝和移动赋值运算符定义了将一个对象赋予另一个对象时做什么,析构函数则定义当此类型销毁时做什么,称这些操作为拷贝控制操作;

合成拷贝构造函数:如果我们没有定义拷贝构造函数,与合成默认构造函数不同(只要有其他构造函数定义,编译器不会帮你生成合成默认构造函数),即使我们定义了其他构造函数,编译器也会为我们生成合成拷贝构造函数,合成拷贝构造函数会将其参数逐个拷贝到正在创建的对象中,编译器从给定的对象一次将每个非static成员拷贝到正在创建的对象中,

拷贝规则:对类类型的成员调其拷贝构造函数,内置类型成员直接拷贝.

拷贝初始化和直接初始化区别:

直接初始化:实际上是要求编译器使用普通的函数匹配来选择与我们的参数最匹配的构造函数;

拷贝初始化:实际上要求编译器将右侧的对象拷贝到正在创建的对象中,如果需要还要进行类型转换(调"构造函数"匹配建立左侧对象->调 "拷贝构造函数/移动拷贝构造函数" 将右侧对象拷贝);

总结:直接初始化:一对小括号加参数。拷贝初始化:等号右侧对象拷贝到正在创建的对象中,如果需要还需进行类型转换;

注意:调用构造函数!=直接初始化,调用拷贝构造构造!=拷贝初始化

直接初始化:一般在 "()" 调用时发生

拷贝初始化不仅在我们使用 "=" 时发生,下列三种情况也会发生

1.将一个对象作为实参传递给非引用类型的形参时.

2.从一个返回类型为非引用类型的函数返回参数.

3.使用花括弧列表初始化一个数组中的元素或聚合类的成员

拷贝构造函数参数是 "const 类型&" 如果不是,实参传递过程中则需要拷贝,则需要循环拷贝,所以必须 "const 类型&" ;

拷贝初始化限制:其实只要都加上explicit来强制显示调用,就可以不用管拷贝初始化或者直接初始化了;

编译器可以绕过拷贝构造函数:在拷贝初始化过程中,编译器可以略过(但不是必须)拷贝/移动构造函数,直接创建对象

C/C++:编译器将把 std::string str="123sadw2-asd"; 改成这样 std::string str("123sadw2-asd"); 虽然这些拷贝构造略过了,但拷贝/移动构造必须是可以被访问的;

C/C++(constructor/copy constructor 表示打印调用):

 1 #include <iostream>
 2 #include <string>
 3
 4
 5 class CopyClass
 6 {
 7 public:
 8     std::string str_;
 9 public:
10     CopyClass(const std::string &str = std::string())
11             : str_(str)
12     {
13         std::cout << str_ << " constuctor CopyClass" << std::endl;
14     }
15
16     CopyClass(const CopyClass &rhs)
17             : str_(rhs.str_)
18     {
19         std::cout << str_ << " copy constructor CopyClass" << std::endl;
20     }
21
22 };
23
24 int main(int argc, char *argv[])
25 {
26     CopyClass A("A");               //explicit constructor
27     CopyClass U{"D"};               //explicit constructor
28
29     CopyClass E = CopyClass("D");     //explictt constructor
30     CopyClass B(A);                   //explicit copy constructor
31     CopyClass C = B;                  //imolicit copy constructor
32     CopyClass F = {"X"};              //implicit constructor
33     CopyClass D = {C};                //implicit copy constructor
34
35     return 0;
36 }

拷贝赋值运算符,其实就是一个名为 operator= 的函数(operator后加表示要定义的运算符的符号),重载运算符,有返回类型和参数,返回类型通常是左侧运算符的引用(为了和内置类型赋值返回本身保持一致),未定义拷贝赋值运算符的话编译器会帮你生成一个合成拷贝赋值运算符,内部实现也是把每个非static变量赋值给左侧对象

析构函数:由"~类型名()"组成,与构造函数执行相反顺序,由一个函数体和析构部分执行,函数体可以执行一些释放动态内存的操作,析构部分属于函数体制外执行的,逆序释放成员变量,内置类型没有析构函数,复合类型调用它们自己的析构函数,当然如果没有定义析构函数,编译器也会为你提供合成析构函数,但是不会为你释放动态内存=申请的内存;

注意点1:析构函数体自身并不直接销毁成员,是在析构函数体执行完毕之后隐式的析构阶段中被销毁的

注意点2:隐式销毁一个内置指针类型的成员不会delete它所指的对象

注意点3:当 指向一个对象的引用或指针离开作用域,析构函数不会执行

调用析构函数的情况:

1:变量离开作用域时被销毁

2:当对象被销毁,其成员被销毁

3:容器被销毁,成员被销毁

4:动态分配的对象,指针被delete时

5:临时对象,创建的完整表达式结束时

需要析构函数的类也需要拷贝和赋值操作,合成的析构函数不会delete一个指针数据成员,所以有时我们需要自己定义一个析构函数释放构造函数分配的内存,所以需要析构函数的类,也就需要拷贝构造函数和拷贝赋值运算符,而合成的拷贝构造函数和拷贝赋值运算符只能简单的拷贝指针成员,这就意味着多个对象指向同一个内存,释放多个对象时,造成多次delete

如果一个类需要自定义版本的析构函数,那么肯定是需要自定义的拷贝构造函数和拷贝赋值运算符

而且需要拷贝操作也需要复制操作,反之亦然;

我们可以通过将拷贝控制成员定义为 =default 来显式的要求编译器生成合成的版本(只能对有合成版本的函数使用),在此之后,合成的函数将隐式的声明为内联

iostream类阻止了拷贝,避免多个对象同时写入,或读取相同的IO缓冲,我们可以将拷贝构造函数和拷贝赋值运算符定义为删除的函数来阻止拷贝,虽然声明了他们,但不能以任何的方式使用他们,在参数列表之后加上 =delete 来指出我们希望其是被删除的,这是为了通知编译器,我们不希望这些函数被定义

可以对任何类内函数(析构函数除外)声明 =delete ,且必须出现在函数第一次声明的时候,如果析构函数被声明=delete ,析构函数被删除,就无法销毁此类型的对象

C++11之前,是将拷贝构造函数和拷贝赋值运算符定义为private来阻止拷贝的(旧标准)判断一个类是否需要拷贝控制函数成员,首先判断其是否需要自定义版本的析构函数,如果需要,则拷贝控制成员函数都需要

以上内容主要来自C++primer

定义行为像值的类:为了提供类值行为,对于类管理的资源,每个对象都应该拥有一份自己的拷贝

类值拷贝赋值运算符:赋值运算符通常组合了析构函数和构造函数,类似析构函数,赋值操作会销毁左侧对象,类似拷贝构造函数,赋值操作会从右侧对象拷贝数据,但是非常重要的一点这些操作顺序是以正确的顺序执行的,即使将一个对象赋予它本身也是正确的(保证C++异常安全),当异常发生时能将左侧运算对象置于一个有意义的状态

步骤:先拷贝右侧对象(此时异常也能保证源对象安全),然后释放左侧对象,把资源指针变量重新指向或赋值;

当你编写赋值运算符的时,记住两点:

1.如果将一个对象它本身赋予自身,赋值运算符也要保证必须能正确工作

2.大多数赋值运算符组合了析构函数和拷贝函数的工作

C/C++:

 1 //类值行为
 2 class HasPtr
 3 {
 4 private:
 5     int value;
 6     std::string *str;
 7 public:
 8     HasPtr(const std::string &str_ = std::string(), const int &value_ = int())
 9             : str(new std::string(str_)),
10               value(value_)
11     {
12
13         //构造对象,先构造完对象在执行构造函数体,所以函数体内一般属于赋值而不是初始化,并根据
14         //声明顺序初始化而不是形参或列表初始化顺序;
15
16     }
17
18     ~HasPtr()
19     {
20         //动态内存对象需自己手动delete释放(智能指针除外)
21         delete str;
22         //析构对象,先执行析构函数体后执行析构成员部分(隐式的,分离的);
23         //析构成员按照声明顺序的逆序析构,内置类型无析构函数,类对象执行析构函数
24     }
25
26     //拷贝构造(以值方式传递,副本与原版互不影响)
27     HasPtr(const HasPtr &other)
28             : str(new std::string(*(other.str))),
29               value(other.value)
30     {
31
32     }
33
34     HasPtr &operator=(const HasPtr &other)
35     {
36         std::string *ptr = new std::string(*(other.str));
37         delete str;
38         str = ptr;
39         value = other.value;
40
41         return *this;
42     }
43 };

定义行为像指针的类:我们需要更改拷贝构造函数/拷贝复制运算符来改变指针的指向而是改变内存,而析构函数则根据内存指针是否是左后一个拥有者来释放动态内存,类似shared_ptr<T>,利用引用计数来判别;

C/C++

//类指针行为(共享指针)
class HasPtr_
{friend void swap(HasPtr_ &lhs, HasPtr_ &rhs);friend bool operator<(const HasPtr_ &lhs, const HasPtr_ &rhs);private:int value;std::string *str;std::size_t *use;
public:HasPtr_(const std::string &str_ = std::string()): value(0),str(new std::string(str_)),use(new std::size_t(1)){}HasPtr_(const HasPtr_ &rhs): value(rhs.value),str(rhs.str),use(rhs.use){++*use;}//对象退出作用域时会判断引用计数~HasPtr_(){if (*use == 0){delete str;delete use;}//然后退出函数体执行其他成员变量析构
    }HasPtr_ &operator=(const HasPtr_ &rhs){++*(rhs.use);     //先递增右对象引用计数;if (--*use == 0)     //判断递减左对象是否是唯一拥有则释放指针
        {delete str;delete use;}//然后赋值操作value = rhs.value;str = rhs.str;use = rhs.use;return *this;}void display(){std::cout << *str << std::endl;}};

交换操作:当我们调用stl一些算法时类似sort这样的,如果编译器没有找到类自己提供的交换函数swap,则会调用标准库的std::swap,而标准库的swap操作过程会出现一次拷贝,两次赋值操作(类似值

交换),但这样的操作对于有内存分配的类来说资源浪费,因为交换两对象的资源只要互换指针就行,而不用重复申请内存这样的.

C/C++:

 1 void swap(HasPtr_ &lhs, HasPtr_ &rhs)
 2 {
 3     std::cout << "HasPtr swap()" << std::endl;
 4     //声明为friend,需要使用私有变量
 5     //声明使用外部swap,防止递归
 6     using std::swap;
 7     swap(lhs.str, rhs.str);
 8     swap(lhs.use, rhs.use);
 9     swap(lhs.value, rhs.value);
10
11 }
12
13 bool operator<(const HasPtr_ &lhs, const HasPtr_ &rhs)
14 {
15     return lhs.str->length() < rhs.str->length();
16 }

总结:看完一章总结有点乱,暂且作为记录.....

转载于:https://www.cnblogs.com/xuaidongstdudyrecording/p/7193972.html

C/C++:copy control (拷贝控制)相关推荐

  1. c/c++ 拷贝控制 构造函数的问题

    拷贝控制 构造函数的问题 问题1:下面①处的代码注释掉后,就编译不过,为什么??? 问题2:但是把②处的也注释掉后,编译就过了,为什么??? 编译错误: 001.cpp: In copy constr ...

  2. 【C++】C++的拷贝控制

    目录结构: contents structure [-] 拷贝.赋值与销毁 拷贝构造函数 拷贝初始化 参数和返回值 拷贝赋值运算符 析构函数 三五法则 拷贝控制和资源管理 交换操作 对象移动 右值引用 ...

  3. C++ Primer 5th笔记(chap 13 拷贝控制) 实例2内存管理

    1. 目标 实现标准库vector类的一个简化版本,只支持string,我们命名为StrVec. 2. 设计思想 2.1 allocator管理每个StrVec对象的内存池, 是一块连续的内存(类型为 ...

  4. C++ Primer 5th笔记(chap 13 拷贝控制)实例1

    1. Folder和Message的类设计 2. Messager.h class Message {friend void swap(Message&, Message&);frie ...

  5. c++ primer 5th第13章拷贝控制知识点和自编习题答案

    首先,先给大家提个醒.在网上的随书源代码里关于hasptr类的类指针版本编写的移动构造函数.移动赋值运算符.和析构函数部分是有错误的.大家可以把hasptr累指针版本(里面带移动构造函数和移动赋值运算 ...

  6. C++中类的拷贝控制

    C++中类的拷贝控制 转自:https://www.cnblogs.com/ronny/p/3734110.html 1,什么是类的拷贝控制 当我们定义一个类的时候,为了让我们定义的类类型像内置类型( ...

  7. 包,logging日志模块,copy深浅拷贝

    一 包 package 包就是一个包含了 __init__.py文件的文件夹 包是模块的一种表现形式,包即模块 首次导入包: 先创建一个执行文件的名称空间 1.创建包下面的__init__.py文件的 ...

  8. 《C++ Primer》读书笔记——第十三章_拷贝控制

    一个类有5种特殊的成员函数:拷贝构造函数.拷贝赋值运算符.移动构造函数.移动赋值运算符.析构函数.如果没有定义这些拷贝控制成员,编译器会自动为它定义缺失的操作. A a; A b = a;//报错 1 ...

  9. 【C++】拷贝控制与资源管理

    1. 拷贝控制与资源管理 管理类外资源的类必须定义拷贝控制成员.如P447中所见,这种类需要通过析构函数来释放对象所分配的资源.一旦一个类需要析构函数,那么几乎可确定它也需要一个拷贝构造函数和一个拷贝 ...

最新文章

  1. Typora输出表情 Typora_Smile
  2. R语言成功加载rJava方法
  3. 关于Keil 的快速注释功能,并为其添加快捷键
  4. list所有元素相加 python_Python基础入门笔记:列表、元组(阿里云天池)
  5. 如何将SmartDraw中的图形导出LATEX可用的EPS格式?
  6. html5判断设备的动作
  7. spring 4.0 JUnit简单的Dao,Service测试
  8. 【转】注册Azure AD 应用程序
  9. 分号可以用来分段么_更高效的GMX分段模拟方法:修改tpr文件
  10. Qt文档阅读笔记-QCompleter官方解析及实例
  11. 1. OD-界面视图及基本快捷键操作,修改hello word
  12. Going to 的将来时态_47
  13. 全程2分钟!教你如何免费下载Windows 10
  14. 忆2015,迎2016(致敬自己)
  15. CNware存储管理功能介绍
  16. 基于KNN的手写数字识别
  17. python 生成词云
  18. python获取当前系统的日期_Python获取当前日期时间
  19. 【CCF推荐专区】计算机类优质SCIEI好刊,期刊质量高,部分期刊仅有少量版面
  20. 如何推广淘宝店铺方法:导购网站免费推广

热门文章

  1. Mysql数据库中的 Group by 语句的特殊之处(select 中的项目不必出现在Group by中)---不建议使用!
  2. salesforce 学习(超简介,以及传送门)
  3. Eclipse 中 去掉控制台最大行数限制
  4. 【PostgreSQL-9.6.3】表继承
  5. 网络管理与维护作业2
  6. 使用celery出现async的报错的解决方法
  7. 解决WORD文档无法显示链接的图像问题
  8. Win7 IIS7 HTTP 错误 404.2 - Not Found解决方法
  9. 手机端局部滚动问题 overflow-y:auto
  10. 循环报数java代码_循环报数 Java实现