C/C++:copy control (拷贝控制)
前言:当定义一个类的时候,我们显示或者隐式地指定在此类型的对象拷贝,移动,赋值,销毁时做些什么,一个类通过定义五种特殊的成员函数来控制这些操作,包括拷贝构造函数,拷贝赋值运算符,移动构造函数,移动赋值运算符和析构函数, 拷贝和移动构造函数定义了同类型的另一个对象初始化本对象时做什么,拷贝和移动赋值运算符定义了将一个对象赋予另一个对象时做什么,析构函数则定义当此类型销毁时做什么,称这些操作为拷贝控制操作;
合成拷贝构造函数:如果我们没有定义拷贝构造函数,与合成默认构造函数不同(只要有其他构造函数定义,编译器不会帮你生成合成默认构造函数),即使我们定义了其他构造函数,编译器也会为我们生成合成拷贝构造函数,合成拷贝构造函数会将其参数逐个拷贝到正在创建的对象中,编译器从给定的对象一次将每个非static成员拷贝到正在创建的对象中,
拷贝规则:对类类型的成员调其拷贝构造函数,内置类型成员直接拷贝.
拷贝初始化和直接初始化区别:
直接初始化:实际上是要求编译器使用普通的函数匹配来选择与我们的参数最匹配的构造函数;
拷贝初始化:实际上要求编译器将右侧的对象拷贝到正在创建的对象中,如果需要还要进行类型转换(调"构造函数"匹配建立左侧对象->调 "拷贝构造函数/移动拷贝构造函数" 将右侧对象拷贝);
总结:直接初始化:一对小括号加参数。拷贝初始化:等号右侧对象拷贝到正在创建的对象中,如果需要还需进行类型转换;
注意:调用构造函数!=直接初始化,调用拷贝构造构造!=拷贝初始化
直接初始化:一般在 "()" 调用时发生
拷贝初始化不仅在我们使用 "=" 时发生,下列三种情况也会发生
1.将一个对象作为实参传递给非引用类型的形参时.
2.从一个返回类型为非引用类型的函数返回参数.
3.使用花括弧列表初始化一个数组中的元素或聚合类的成员
拷贝构造函数参数是 "const 类型&" 如果不是,实参传递过程中则需要拷贝,则需要循环拷贝,所以必须 "const 类型&" ;
拷贝初始化限制:其实只要都加上explicit来强制显示调用,就可以不用管拷贝初始化或者直接初始化了;
编译器可以绕过拷贝构造函数:在拷贝初始化过程中,编译器可以略过(但不是必须)拷贝/移动构造函数,直接创建对象
C/C++:编译器将把 std::string str="123sadw2-asd"; 改成这样 std::string str("123sadw2-asd"); 虽然这些拷贝构造略过了,但拷贝/移动构造必须是可以被访问的;
C/C++(constructor/copy constructor 表示打印调用):
#include <iostream>
#include <string> class CopyClass
{
public:
std::string str_;
public:
CopyClass(const std::string &str = std::string())
: str_(str)
{
std::cout << str_ << " constuctor CopyClass" << std::endl;
} CopyClass(const CopyClass &rhs)
: str_(rhs.str_)
{
std::cout << str_ << " copy constructor CopyClass" << std::endl;
} }; int main(int argc, char *argv[])
{
CopyClass A("A"); //explicit constructor
CopyClass U{"D"}; //explicit constructor CopyClass E = CopyClass("D"); //explictt constructor
CopyClass B(A); //explicit copy constructor
CopyClass C = B; //imolicit copy constructor
CopyClass F = {"X"}; //implicit constructor
CopyClass D = {C}; //implicit copy constructor return ;
}

拷贝赋值运算符,其实就是一个名为 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++:
//类值行为
class HasPtr
{
private:
int value;
std::string *str;
public:
HasPtr(const std::string &str_ = std::string(), const int &value_ = int())
: str(new std::string(str_)),
value(value_)
{ //构造对象,先构造完对象在执行构造函数体,所以函数体内一般属于赋值而不是初始化,并根据
//声明顺序初始化而不是形参或列表初始化顺序; } ~HasPtr()
{
//动态内存对象需自己手动delete释放(智能指针除外)
delete str;
//析构对象,先执行析构函数体后执行析构成员部分(隐式的,分离的);
//析构成员按照声明顺序的逆序析构,内置类型无析构函数,类对象执行析构函数
} //拷贝构造(以值方式传递,副本与原版互不影响)
HasPtr(const HasPtr &other)
: str(new std::string(*(other.str))),
value(other.value)
{ } HasPtr &operator=(const HasPtr &other)
{
std::string *ptr = new std::string(*(other.str));
delete str;
str = ptr;
value = other.value; return *this;
}
};
定义行为像指针的类:我们需要更改拷贝构造函数/拷贝复制运算符来改变指针的指向而是改变内存,而析构函数则根据内存指针是否是左后一个拥有者来释放动态内存,类似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(),
str(new std::string(str_)),
use(new std::size_t())
{ } HasPtr_(const HasPtr_ &rhs)
: value(rhs.value),
str(rhs.str),
use(rhs.use)
{
++*use;
} //对象退出作用域时会判断引用计数
~HasPtr_()
{
if (*use == )
{
delete str;
delete use;
} //然后退出函数体执行其他成员变量析构
} HasPtr_ &operator=(const HasPtr_ &rhs)
{
++*(rhs.use); //先递增右对象引用计数;
if (--*use == ) //判断递减左对象是否是唯一拥有则释放指针
{
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++:
void swap(HasPtr_ &lhs, HasPtr_ &rhs)
{
std::cout << "HasPtr swap()" << std::endl;
//声明为friend,需要使用私有变量
//声明使用外部swap,防止递归
using std::swap;
swap(lhs.str, rhs.str);
swap(lhs.use, rhs.use);
swap(lhs.value, rhs.value); } bool operator<(const HasPtr_ &lhs, const HasPtr_ &rhs)
{
return lhs.str->length() < rhs.str->length();
}
总结:看完一章总结有点乱,暂且作为记录.....
C/C++:copy control (拷贝控制)的更多相关文章
- [c++] Copy Control
C++ allows the programmer to define how objects are to be copied, moved, assigned and destroyed. Tog ...
- C++之拷贝控制 (Copy Control)
只有2种成员 值成员: 指针成员: 依实现可分为raw pointer / shared_ptr; 现在,仅考虑第③种:资源对象共享 角度来考虑拷贝控制 类的两种语义:值语义.似指针 编译器提供的de ...
- C++的那些事:类的拷贝控制
1,什么是类的拷贝控制 当我们定义一个类的时候,为了让我们定义的类类型像内置类型(char,int,double等)一样好用,我们通常需要考下面几件事: Q1:用这个类的对象去初始化另一个同类型的对象 ...
- C++ Primer : 第十三章 : 拷贝控制之拷贝、赋值与销毁
拷贝构造函数 一个构造函数的第一个参数是自身类类型的引用,额外的参数(如果有)都有默认值,那么这个构造函数是拷贝构造函数.拷贝构造函数的第一个参数必须是一个引用类型. 合成的拷贝构造函数 在我们没 ...
- c/c++ 拷贝控制 构造函数的问题
拷贝控制 构造函数的问题 问题1:下面①处的代码注释掉后,就编译不过,为什么??? 问题2:但是把②处的也注释掉后,编译就过了,为什么??? 编译错误: 001.cpp: In copy constr ...
- c/c++ 拷贝控制 右值与const引用
拷贝控制 右值与const引用 背景:当一个函数的返回值是自定义类型时,调用侧用什么类型接收?? 1,如果自定义类型的拷贝构造函数的参数用const修饰了:可以用下面的方式接收. Test t2 = ...
- 【C++ Primer | 15】构造函数与拷贝控制
合成拷贝控制与继承 #include <iostream> using namespace std; class Base { public: Base() { cout << ...
- C/C++基础----拷贝控制
拷贝控制操作,有5个特殊成员函数copy ctor,copy =opt,move ctor,move =opt,dtor 有哪些地方会用到 拷贝初始化 除了=定义变量时 参数传递和函数返回时 花括号列 ...
- 【C++】C++的拷贝控制
目录结构: contents structure [-] 拷贝.赋值与销毁 拷贝构造函数 拷贝初始化 参数和返回值 拷贝赋值运算符 析构函数 三五法则 拷贝控制和资源管理 交换操作 对象移动 右值引用 ...
随机推荐
- 关于java内存泄露的总结--引用的类型:强引用,弱引用,软引用
今天面试了一家公司的java开发方面的实习生,被问到一个问题:如何处理java中的内存泄露问题,保证java的虚拟机内存不会被爆掉,当时其实觉得面试官的问题有点泛,所以也没有很好领会他的意思,答案也不 ...
- jenkins配置01--用户添加及权限配置
原文出自:https://www.cnblogs.com/kevingrace/p/6019707.html 下面重点记录下jenkins安装后的一些配置: (1)添加用户权限 jenkins初次登陆 ...
- 微信小程序——节奏练耳 宣传页
节奏练耳是什么? 节奏练耳小程序是一款听音练习节奏的交互式小程序.节奏练耳第一大节是辨认六种音符的练习,剩余九大节的练习题中播放的音频是将时值长短不一的音符组合在一起,配合相应的节奏图片,以提高辨认节 ...
- 第三次博客作业JSF
JSF规格化设计发展史以及为什么得到人们重视 查阅了n多资料但是仍然没找到. 就说一些jsf的优势吧. 优势: (1)UI组件 (2)事件驱动模式 (3)用户界面到业务逻辑的直接映射 (4)程序 ...
- 软件项目的开发之svn的使用
Svn简介 SVN全名Subversion,即版本控制系统.SVN与CVS一样,是一个跨平台的软件,支持大多数常见的操作系统.作为一个开源的版本控制系统,Subversion管理着随时间改变的数据.这 ...
- NetFPGA-SUME下reference_nic测试
Reference_nic Reference_nic是NetFPGA-SUME中提供的一个参考Demo,本文主要介绍如何构建并在SUME上运行reference_nic. GIT源 git clon ...
- 项目Beta冲刺(团队)第六天
1.昨天的困难 可以获得教务处通知栏的15条文章数据了,但是在显示的时候出了问题. 私信聊天的交互还没研究清楚 2.今天解决的进度 成员 进度 陈家权 研究私信模块 赖晓连 研究问答模块 雷晶 研究服 ...
- alpha阶段总结 (第一阶段冲刺成果)
首次接触手机APP的制作,虽然很多都不懂,但是在网上查阅相关知识和询问同学的帮助下,我们团队总算对此有相当的了解,但是因为时间问题,首次冲刺的成果不大,我们相信在下一次的冲刺中会给出更好的效果出来. ...
- 《TCP/IP 详解 卷1:协议》第 8 章:Internet 控制报文协议
路由器是 Internet 的重要组成部分,严密监视 Internet 的操作.IP 协议未给发送失败的 IP 数据包提供一种错误处理,也没有给端系统提供直接的方法来发现错误.为了解决这一不足之处,I ...
- HDU 2086 A1 = ?
http://acm.hdu.edu.cn/showproblem.php?pid=2086 Problem Description 有如下方程:Ai = (Ai-1 + Ai+1)/2 - Ci ( ...