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 [-] 拷贝.赋值与销毁 拷贝构造函数 拷贝初始化 参数和返回值 拷贝赋值运算符 析构函数 三五法则 拷贝控制和资源管理 交换操作 对象移动 右值引用 ...
随机推荐
- vmware安装androidx86 (FreeBSD) 系统图解
有时候自己手机的一些方面限制的因素,我们需要在电脑上装一个“手机”,来完成我们想要做的事情. 安装步骤如下: 首先需要一个ISO系统镜像,下面地址可以提供大量镜像下载: https://zh.osdn ...
- Django缓存配置和使用
- 缓存 - 配置 CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCAT ...
- iOS 开发学习-类的创建与实现,与java语言的对比
Person.h #import <Foundation/Foundation.h> @interface Person : NSObject { //在{}中定义属性(全局变量/实例变量 ...
- 第三次作业---excel导入数据库及显示(2)
发现第一次做的功能有点复杂,不能理解.而且第一次的想法是在页面上上传文件,连接并导入到数据库,并在页面上显示.后来才看到要求是直接在本地将数据导入数据库就行了,然后显示.所以才出现了一堆看不懂也解决不 ...
- 我是一名IT小小鸟
我是一只it小小鸟 书中介绍了it界大牛们大学期间的学习方法和对未来的职业规划,相比他们,自我感觉相距甚远,对这学科的热情程度也远远比不上他们. 就拿目前数据结构这门高深的课程,应通过更多的课外扩展来 ...
- 用JAVA制作微型操作系统4月23日情况
弄好了一个自认为十分精美的界面,但本想着昨天就在开始按钮上先套入控制jp222面板上的jb2标签上的时间更新,这按钮起到开始线程的作用(我认为按钮应该可以通过t.start()来触发线程,结果不知为什 ...
- vue.js常用指令
本文摘自:http://www.cnblogs.com/rik28/p/6024425.html Vue.js的常用指令 上面用到的v-model是Vue.js常用的一个指令,那么指令是什么呢? Vu ...
- angularJS1笔记-(14)-自定义指令(scope)
index.html: <!DOCTYPE html> <html lang="en"> <head> <meta charset=&qu ...
- Sprint1回顾
Sprint目标 此产品为适用于小学生使用的四则运算训练软件.关于第一期Sprint冲刺的目标,我们打算实现产品的以下几点的功能: •1.初始界面设计 •2.四则基本运算算法 •3.能产生随机式子 • ...
- 【Leetcode】82. Remove Duplicates from Sorted List II
Question: Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only dis ...