C++拷贝控制技术
Posted lvshy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++拷贝控制技术相关的知识,希望对你有一定的参考价值。
- 模板特例如何写忘了
- 拷贝noexcept
如果可以确认不会抛出异常,交换两个指针就行了
- swap是命名空间里的某个,更改的时候需要加上该命名空间
特例函数怎么写,就这样。inline的位置值得注意
- "=”运算符重载
-
两种拷贝一致
-
不可拷贝的方式
- 编译器会想方设法为我们设置一个默认的拷贝
- 有一个不可拷贝的成员对象
- 继承自一个不可拷贝的类
- 任何对象至少有一字节 空的类也不会0
- 空基类优化 继承自一个空类它就真的是空的,编译器帮我们优化
- 所以我们采用继承的方式
因为它的基类无法生成拷贝
- 继承是个秘密
任何依赖于知道继承的关系的操作都不行了
- 抽象类操作
- 返回值可以是对象的指针,要与抽象类一致或者是斜变
-
- const要从逻辑来考虑,不是仅仅是元素
按理来说可以写成const因为shape类的数据成员是bp,bp其实没有被改变,没有被改变意思是说吧bp指向的对象没变,只是对象的数据改变了。但是用户理解是它伸缩了
- 代理类surrogate
- 支持动态绑定
- 内存管理,不用手动释放
- 引用计数
- 一个人什么时候真正死亡,是他的肉体死亡吗?不是,当这个世界上没有任何一个人再记住他的时候
- 值语义、引用语义
- 句柄:一种代理类 添加引用计数,当计数为0销毁对象
- 懒惰赋值
- 拷贝新的开始 没有销毁就不会创建
- 发生了几次拷贝
自17起不再把返回值拷贝
- 拷贝控制
- 拷贝构造函数
写参数的时候一定要首先考虑可不可以用常量引用,如果不写引用,他会首先发生临时变量赋值,这时候就会调用拷贝构造函数,无限递归了。如果不写const首先他不规范,其次去除底层const
- 拷贝复制运算符
-
返回this引用可以连续赋值,这是一种规范
- 赋值运算符重载一定要避免自我复制 V1=V1看起来很蠢,但是V1可能等于它的引用
当首先销毁自身时也同时销毁了赋值对象
-
正确的做法 先复制,异常安全
- 不是异常安全的看似的做法
-
- 拷贝构造函数
- 发生了几次拷贝
- 默认的拷贝构造
- 逐个成员复制
- 类对象,调用类的拷贝构造
- 不可拷贝对象,没办法也不报错
- 数组成员可以正确拷贝,编译器知道它是数组,指针就不是数组
- 默认函数,编译器有一定能力合成的函数=default
- 三大律
这个类有一定的资源,或者这三个函数不好写、
- 禁止拷贝
- 比如输入流cin
- =delete 所有函数都可以=delete
如果编译器帮你生成默认函数发生错误时隐式delete
- 之前是把拷贝弄成private,类内或者friend 只声明不定义,编译器发生连接错误,连接错误是运行错误,不是编译错误
- explicit声明
显式构造,不允许隐式类型转换
- logic const
不是const,可变对象,这个对象可不变对用户没有任何影响,在逻辑上依然是const,但是如果不写mutable肯定是不行的,因为编译器不允许在const里更改修改值
- decltype类型推导
只用类型,不去计算,完完整整保留
i加个()就是表达式,表达式的返回值就是左值是引用
拷贝控制C++
第13章 拷贝控制
从此章我们即将开始第三部分的学习,之前我们已经学过了两个部分,C++基础和C++标准库,第三部分为类设计者的工具
也就是我们即将开始传说中的对象对象编程之旅,面向对象程序设计(Object Oriented Programming)
本章进行学习类如何操控该类型的拷贝,赋值,移动或者销毁,有:拷贝构造函数、移动构造函数、拷贝赋值运算符、移动赋值运算符以及析构函数等重要知识
拷贝构造函数
定义:如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是构造拷贝函数
简单上手
//example1.cpp
class Person
public:
int age;
Person() = default;
Person(int age) : age(age)
Person(const Person &person)
//内容拷贝
this->age = person.age;
;
int main(int argc, char **argv)
Person person1(19);
Person person2 = person1;
cout << person2.age << endl; // 19
return 0;
合成拷贝构造函数
默认情况下,编译器会定义一个拷贝构造函数,即使在我们提供拷贝构造函数的情况下也仍会自动生成,默认情况下会将每个非static成员拷贝到正在创建的对象中
//example2.cpp
class Person
public:
int age;
string name;
Person() = default;
Person(const Person &);
Person(const int age, const string name) : age(age), name(name)
;
//直接使用构造函数初始化列表
//此定义与默认合成拷贝函数相同
Person::Person(const Person &person) : age(person.age), name(person.name)
int main(int argc, char **argv)
Person me(19, "gaowanlu");
Person other = me;
// 19 gaowanlu
cout << other.age << " " << other.name << endl;
return 0;
尝试测试一下编译器默认提供的合成拷贝构造函数,可见存在默认合成拷贝构造函数
如果不想让一个构造函数具有可以赋值转换的功能,则将其定义为explicit的
//example3.cpp
class Person
public:
string name;
int age;
Person(const int age, const string name) : name(name), age(age)
;
int main(int argc, char **argv)
Person me(19, "gaowanlu");
Person other = me;
// 19 gaowanlu
cout << other.age << " " << other.name << endl;
return 0;
重载赋值运算符
重载operator=
方法进行自定义赋值运算符使用时要做的事情
//example4.cpp
class Person
public:
int age;
string name;
Person() = default;
Person(int age, string name) : age(age), name(name)
Person &operator=(const Person &);
;
Person &Person::operator=(const Person &person)
cout << "operator =" << endl;
this->age = person.age;
this->name = person.name;
return *this;
int main(int argc, char **argv)
Person person1(19, "me");
Person person2;
person2 = person1; // operator =
cout << person2.age << " " << person2.name << endl; // 19 me
return 0;
合成拷贝赋值运算符
与合成拷贝构造函数类似,如果没有自定义拷贝赋值运算符,编译器会自动生成
//example5.cpp
class Person
public:
int age;
string name;
Person() = default;
Person(int age, string name) : age(age), name(name)
;
int main(int argc, char **argv)
Person person1(19, "me");
Person person2;
person2 = person1; //使用默认合成拷贝赋值运算符
cout << person2.age << " " << person2.name << endl; // 19 me
return 0;
析构函数
析构函数与构造函数不同,构造函数初始化对象的非static数据成员,还可能做一些在对象创建时需要做的事情。析构函数通常释放对象的资源,并销毁对象的非static数据成员
~TypeName();
析构函数没有返回值,没有接收参数,所以其没有重载形式
在构造函数中,初始化部分执行在函数体执行前,析构函数则是首先执行函数体,然后按照初始化顺序的逆序销毁。
构造函数被调用的时机
- 变量在离开其作用域时被销毁
- 当一个对象被销毁时,其成员被销毁
- 容器(无论标准容器还是数组)被销毁时,其元素被销毁
- 动态内存分配,当对它的指针使用delete时被销毁
- 对于临时对象,当创建它的完整表达式结束时被销毁
//example6.cpp
class Person
public:
int age;
string name;
Person() = default;
Person(int age, string name) : age(age), name(name)
~Person()
cout << "~Person" << endl;
;
Person func(Person person)
return person;
int main(int argc, char **argv)
Person person(19, "me");
Person person1 = func(person);
//~Person被打印三次
//首先将person拷贝给func的形参,然后形参person作为返回值赋值给person1
//然后func返回值person被销毁
//随着main执行完毕,main内的两个Person被销毁
return 0;
合成析构函数
当为自定义析构函数时,编译器会自动提供一个合成析构函数,对于某些类作用为阻止该类型的对象被销毁,如果不是则函数体为空
//example7.cpp
class Person
public:
int age;
string name;
Person() = default;
Person(int age, string name) : age(age), name(name)
~Person() //等价于合成析构函数
;
int main(int argc, char **argv)
Person person(19, "gaowanlu");
cout << person.age << " " << person.name << endl; // 19 gaowanlu
return 0;
在合成析构函数体执行完毕之后,成员会被自动销毁,对象中的string被销毁时,将会调用string的析构函数,将name的内存释放掉,析构函数自身并不直接销毁成员
,是在析构函数体之后隐含的析构阶段中被销毁的,整个销毁过程,析构函数体是作为成员销毁步骤之外的并一部分而进行的
如果对象的内部有普通指针记录new动态内存,在对象析构过程默认只进行指针变量指针本身的释放,而不对申请的内存进行释放,则就需要动态内存章节学习的在析构函数体内手动释放他们,或者使用智能指针,随着智能指针的析构被执行,动态内存会被释放
三/五法则
有三个基本操作可控制类的拷贝操作:拷贝构造函数、拷贝赋值运算符、析构函数。在新标准下还可以通过定义一个移动构造函数、一个移动赋值运算符
我们发现有时赋值运算符与拷贝构造函数会执行相同的功能,通常情况下并不要求定义所有这些操作
使用合成拷贝函数和合成拷贝赋值运算符时可能遇见的问题
//example8.cpp
class Person
public:
int age;
string *name;
Person(const string &name = string()) : name(new string(name)), age(0)
~Person()
delete name;
;
int main(int argc, char **argv)
Person person1("me");
Person person2 = person1; //使用合成拷贝构造函数
//此时的person1.name与person2.name指向相同的内存地址
*person1.name = "he";
cout << *person2.name << endl; // he
cout << "end" << endl; // end
return 0;
在合成拷贝构造函数和合成拷贝赋值运算符,其中的拷贝操作都是简单的指针地址赋值,而不是重新开辟空间,再将原先的name赋值到新的内存空间
使用=default
使用=default
可以显式要求编译器生成合成拷贝构造函数和拷贝赋值运算符
//example9.cpp
class Person
public:
Person() = default; //合成默认构造函数
Person(const Person &) = default; //合成拷贝构造函数
Person &operator=(const Person &); //合成拷贝赋值运算
~Person() = default; //合成析构函数
;
//默认在类内使用=default的成员函数为内联的
//如果不希望是内联函数则应在类外部定义使用=default
Person &Person::operator=(const Person &person) = default;
int main(int argc, char **argv)
Person person1;
Person person2 = person1;
cout << "end" << endl; // endl
return 0;
=delete阻止拷贝
使用=delete
定义删除的函数
//example10.cpp
class Person
public:
Person() = default;
Person(const Person &) = delete; //禁止拷贝构造函数
Person &operator=(const Person &) = delete; //阻止拷贝赋值
~Person() = default;
;
int main(int argc, char **argv)
Person person1;
// Person person2 = person1;//错误 不允许拷贝复制赋值
return 0;
析构函数不能是删除的成员
,否则就不能销毁此类型,没有析构函数的类型可以使用动态分配方式创建,但是不能被销毁
//example11.cpp
class Person
public:
int age;
string name;
Person(const int age, const string name) : age(age), name(name)
~Person() = delete;
;
int main(int argc, char **argv)
Person *person = new Person(19, "me");
// delete person;//错误 Person没有析构函数
return 0;
编译器将成员处理为删除的
对于某些情况,编译器会将合成的成员定义为删除的函数
重点:如果一个类有数据成员不能默认构造、拷贝、复制、销毁,则对应的成员函数将被定义为删除的
private拷贝控制
在新标准之前没有,删除的成员,类是通过将其拷贝构造函数和拷贝赋值运算符声明为private的来阻止拷贝的
//example12.cpp
class Person
private:
Person(const Person &person);
Person &operator=(const Person &person);
public:
int age;
string name;
Person(const int age, const string name) : age(age), name(name)
~Person() = default;
Person() = default;
void test();
;
Person::Person(const Person &person)
Person &Person::operator=(const Person &person)
return *this;
void Person::test()
Person *person = new Person(19, "me");
Person person1 = *person; //函数成员或者友元函数可以使用
delete person;
int main(int argc, char **argv)
Person person1(19, "me");
// Person person2 = person1;
// error: 'Person::Person(const Person&)' is private within this context
person1.test();
return 0;
这种虽然类的外部不能使用拷贝构造和拷贝赋值,但是类的友元和成员函数仍可使用二者,同时想要阻止友元函数或者成员函数的使用,则只声明private成员即可不进行定义
//example13.cpp
class Person
private:
Person(const Person &person); //只声明不定义
Person &operator=(const Person &person); //只声明不定义
public:
int age;
string name;
Person(const int age, const string name) : age(age), name(name)
~Person() = default;
Person() = default;
void test();
;
int main(int argc, char **argv)
Person person1(19, "me");
// Person person2 = person1;
// error: 'Person::Person(const Person&)' is private within this context
// 如果函数成员或友元函数使用拷贝构造或者赋值 也会报错
return 0;
总之优先使用=delete这种新的规范,delete是从编译阶段直接解决问题
行为像值的类
有些类拷贝是值操作,是一份相同得副本
//example14.cpp
class Person
public:
int *age;
string *name;
Person(const int &age, const string &name) : age(new int(age)), name(new string(name))
Person() : age(new int(0)), name(new string(""))
Person &operator=(const Person &person);
~Person()
delete age, delete name;
;
Person &Person::operator=(const Person &person)
*age = *person.age;
*name = *person.name;
return *this;
int main(int argc, char **argv)
Person person1(19, "me");
Person person2(20, "she");
person1 = person2;
cout << *person1.age << " " << *person1.name << endl; // 20 she
cout << *person2.age << " " << *person2.name << endl; // 20 she
*person1.name = "gaowanlu";
cout << *person1.age << " " << *person1.name << endl; // 20 gaowanlu
cout << *person2.age << " " << *person2.name << endl; // 20 she
//可见之间此类对象像一种值类型
return 0;
行为像指针的类
有些类拷贝是指针指向的操作,也就是不同的类的成员会使用相同的内存
先来看一种简单使用的情况
//example15.cpp
class Person
public:
int *age;
string *name;
Person() : age(new int(0)), name(new string)
Person(const int &age, const string &name) : age(new int(age)), name(new string(name))
Person &operator=(const Person &person);
;
Person &Person::operator=(const Person &person)
if (age)
delete age;
if (name)
delete name;
age = person.age;
name = person.name;
return *this;
int main(int argc, char 以上是关于C++拷贝控制技术的主要内容,如果未能解决你的问题,请参考以下文章