C++ Primer : 第十三章 : 拷贝控制之拷贝、赋值与销毁
拷贝构造函数
class Sales_data {
public:
Sales_data(const Sales_data&); private:
std::string bookNo;
int units_sold = 0;
double revenue = 0.0; }; Sales_data::Sales_data(const Sales_data& data) : bookNo(data.bookNo),
units_sold(data.units_sold),
revenue(data.revenue)
{
}
string dots(10, '.'); // 直接初始化
string s(dots); // 直接初始化
string s2 = dots; // 拷贝初始化
string null_book = "9-999-99999-9"; // 拷贝初始化
string nines = string(100, '9'); // 拷贝初始化
直接初始化和拷贝初始化的区别:对于直接初始化,实际上时要求编译器调用普通的函数匹配来选择我们所提供的参数最匹配的构造函数。 对于拷贝初始化,要求编译器将右侧正在运算的对象拷贝到正常创建的对象中,需要的话进行类型转换。
- 将一个对象作为参数传递给一个非引用类型的形参
- 将一个对象作为返回值从一个返回值为非引用类型的函数返回时
- 用花括号列表初始化一个数组中的元素或者一个聚合类中的成员
vector<int> v1(10); // 正确,直接初始化
vector<int> v2 = 10; // 错误,接收大小参数的构造函数时explicit的
void f(vector<int>); // f的参数进行拷贝初始化
f(10); // 错误,不能用一个explicit的构造函数拷贝一个实参
f(vector<int>(10)); // 正确,从一个int直接构造一个临时vector
接受一个大小参数的构造函数时explicit的,意味着我们只能进行直接初始化而不能进行拷贝初始化;因此,我们不能用一个explicit的构造函数来拷贝一个参数,这里的拷贝有参数传递和函数的非引用返回值,如果我们希望使用一个explicit的构造函数时,必须显示的使用,比如上述代码的最后一行那样。
string null_book = "123456";
可以改写为:
string null_book("123456");
即使有时候编译器可以跳过拷贝/移动构造函数,但是我们必须保证拷贝/移动构造函数是存在而且是可访问的(例如:不能是private)。
class MyClass{ public: MyClass(int data) : m_data(data){ std::cout << "Construct a MyClass! The m_data = " << m_data << std::endl;} MyClass(const MyClass& myclass){ this->m_data = myclass.m_data; std::cout << "Copy Construct a MyClass that right m_data = " << myclass.m_data << std::endl; } MyClass& operator = (const MyClass& myclass){ this->m_data = myclass.m_data;
std::cout << "copy a MyClass that right m_data = " << myclass.m_data << std::endl;
return *this; } ~MyClass(){ std::cout << "Delete a MyClass that m_data = " << m_data << std::endl; } int getData(){return m_data;}
private: int m_data; }; int main(int argc, char** argv)
{ std::vector<MyClass> vecClass;
MyClass mclass1(1);
MyClass mclass2(2); std::cout << vecClass.capacity() << std::endl; // emplace_back进行直接初始化
vecClass.emplace_back( 1); std::cout << vecClass.capacity() << std::endl; // 由于vecClass的空间不能容下mclass2, 因此先拷贝一份mclass2,然后再析构原来的mclass2
vecClass.push_back(mclass2); std::cout << vecClass.capacity() << std::endl; MyClass* pMyClass = new MyClass(3); // 和上面同理
vecClass.insert(vecClass.cend(), *pMyClass); std::cout << vecClass.capacity() << std::endl; for (size_t i = 0; i < vecClass.size(); ++i)
std::cout << vecClass[i].getData() << std::endl; delete pMyClass; return 0;
}
输出为:
Construct a MyClass! The m_data = 1
Construct a MyClass! The m_data = 2
0
Construct a MyClass! The m_data = 1
1
Copy Construct a MyClass that right m_data = 1
Delete a MyClass that m_data = 1
Copy Construct a MyClass that right m_data = 2
2
Construct a MyClass! The m_data = 3
Copy Construct a MyClass that right m_data = 3
Copy Construct a MyClass that right m_data = 1
Copy Construct a MyClass that right m_data = 2
Delete a MyClass that m_data = 1
Delete a MyClass that m_data = 2
3
1
2
3
Delete a MyClass that m_data = 3
Delete a MyClass that m_data = 2
Delete a MyClass that m_data = 1
Delete a MyClass that m_data = 1
Delete a MyClass that m_data = 2
Delete a MyClass that m_data = 3
在调用emplace_back时,进行直接初始化,因此调用的是构造函数; 调用push_back时,进行拷贝初始化,调用拷贝构造函数,因为此时vecClass的空间不足,因此先拷贝了原来在vecClass中值为1的元素,然后销毁原来的vector,然后再拷贝新的元素,下面的代码中调用insert函数也是这样的道理,由于空间不够,先拷贝旧容器中的元素,然后销毁,再拷贝新的元素。
拷贝赋值运算符
Sales_data trans, accum;
trans = accum; // 使用Sales_data的拷贝赋值运算符
如果类未定义自己的拷贝赋值运算符,则编译器替我们合成一个。
class Foo { public:
Foo& operator =(const Foo&); // 拷贝赋值运算符 // ...
};
为了和内置类型的赋值保持一致,类的拷贝赋值运算符通常返回一个指向其左则对象的引用。应当注意的是,标准库要求保存的类型要具有赋值运算符,且返回值是左则运算对象的引用。
Sales_data& Sales_data::operator = (const Sales_data& data) {
bookNo = data.bookNo; // 调用std::string::operator =
units_sold = data.units_sold; // 使用内置的int赋值
revenue = data.revenue; // 使用内置的double赋值
return *this; // 返回左则运算对象的引用
}
析构函数
class Foo { public:
// 其他操作
~Foo(); // 析构函数
};
析构函数由波浪号加类名组成,没有返回值,不接受参数列表。
{
Sales_data* p = new Sales_data; // p是一个内置指针
auto p2 = make_shared<Sales_data>(); // p2是一个shared_ptr Sales_data item(*p); // 拷贝构造函数将*p拷贝到item中
vector<Sales_data> vec;
vec.push_back(*p2); // 拷贝p2指向的对象,增加p2的计数
delete p; // 对p指向的对象执行析构函数
} // 退出局部作用域,对item、vec、p2调用析构函数
// 销毁p2会递减其引用计数,如果引用计数变为0, 对象被释放
// 销毁vec会销毁它的元素
当指向一个对象的引用或指针离开作用域时,析构函数不会执行。
三/五法则
class HasPtr { public:
HasPtr(const std::string& s = std::string()) : ps(new std::string(s)), i(0){} private:
std::string* ps;
int i;
};
对于HasPtr类来说,其构造函数为ps分配动态内存,合成的析构函数不会delete一个指针数据成员,因此,这个类需要定义自己的析构函数:
~HasPtr() { delete ps; }
参考上面的原则,我们理应给HasPtr定义一个拷贝构造函数和拷贝赋值运算符,如果我们使用合成的拷贝构造函数和拷贝赋值运算符时, 将会发生严重的错误。
HasPtr f (HasPtr hp) {
HasPtr ret = hp; // 拷贝给定的HasPtr
return ret; // ret和hp被释放
}
当f返回时,hp和ret都被释放,两个对象的析构函数都被执行。于是会delete掉ret和hp的指针成员,但这个两个对象包含相同的指针值,因此会导致此指针被delete两次,发生了一个严重的错误; 另外使用函数 f 的返回值作为初始化值的对象时,指针被销毁,指向无效地址。因此,我们需要定义自己的拷贝构造函数和拷贝赋值运算符。
HasPtr& HasPtr(const HasPtr& has) { ps = new std::string(*(has.ps));
i = has.i;
return *this;
}
需要拷贝操作的类也需要赋值操作,反之亦然
=default和=delete
class Sales_data { public:
Sales_data() = default;
Sales_data(const Sales_data&) = default;
Sales_data& operator =(const Sales_data&);
~Sales_data() = default;
// ...
}; Sales_data& Sales_data::operator = (const Sales_data& ) = default;
在类内使用=default修饰成员的声明时,合成的函数隐式的被声明为内联的,如果不希望是内联的,我们应该在类外定义使用=default。
struct NoCopy {
NoCopy() = default;
NoCopy(const NoCopy&) = delete; // 阻止拷贝
NoCopy& operator = (const NoCopy&) = delete; // 阻止赋值
~NoCopy() = default;
// 其他成员
};
=delete告诉编译器,我们不希望定义这些函数。
- 如果类的某个成员的析构函数是删除的或者不可访问的(例如,是private的),则类的合成析构函数被定义为删除的
- 如果类的某个成员的拷贝构造函数是删除的或不可访问的,则类的合成拷贝构造函数被定义为删除的,如果类的某个成员析构函数是删除的或不可访问的,,则类合成的拷贝构造函数也被定义为删除的。
- 如果类的某个成员的拷贝赋值运算符是删除的或不可访问的,或者是类有一个const的或引用成员,则类的合成拷贝赋值运算符被定义为删除的
- 如果类的某个成员的析构函数是删除的或是不可访问的,或是类有一个const成员,它没有类内初始化器且其类型未显示定义默认构造函数 ,或是类有一个引用成员,它没有类内初始化器,则该类的默认构造函数被定义为删除的
C++ Primer : 第十三章 : 拷贝控制之拷贝、赋值与销毁的更多相关文章
- C++Primer 第十三章
//1.当定义一个类时,我们显示地或隐式地指出在此类型的对象(注意这里是此类型的对象,而不包括此类型的指针)拷贝,移动,赋值,销毁时做什么.一个类通过定义五种特殊的成员函数来控制这些操作:拷贝构造函数 ...
- C++ Primer : 第十三章 : 拷贝控制之对象移动
右值引用 所谓的右值引用就是必须将引用绑定到右值的引用,我们通过&&来绑定到右值而不是&, 右值引用只能绑定到即将销毁的对象.右值引用也是引用,因此右值引用也只不过是对象的别名 ...
- C++ Primer : 第十三章 : 拷贝控制之拷贝控制和资源管理
定义行为像值的类 行为像值的类,例如标准库容器和std::string这样的类一样,类似这样的类我们可以简单的实现一个这样的类HasPtr. 在实现之前,我们需要: 定义一个拷贝构造函数,完成stri ...
- 【C++ Primer 第十三章】4. 拷贝控制示例
拷贝控制示例 #include<iostream> #include<string> #include<set> #include<vector> us ...
- [C++]类的设计(2)——拷贝控制(拷贝控制和资源管理)
1.类的行为分类:看起来像一个值:看起来想一个指针. 1)类的行为像一个值,意味着他应该有自己的状态.当我们拷贝一个像值的对象时,副本和原对象是完全独立的.改变副本不会对原有对象有任何影响 ...
- C++ Primer : 第十三章 : 拷贝控制示例
/* Message.h */ #ifndef _MESSAGE_H_ #define _MESSAGE_H_ #include <iostream> #include <strin ...
- C++ Primer : 第十三章 : 动态内存管理类
/* StrVec.h */ #ifndef _STRVEC_H_ #define _STRVEC_H_ #include <memory> #include <string> ...
- [C++ Primer] : 第13章: 拷贝控制
拷贝, 赋值与销毁 当定义一个类时, 我们显示地或隐式地指定在此类型的对象拷贝, 移动, 赋值和销毁时做什么. 一个类通过定义5种特殊的成员函数来控制这些操作, 包括: 拷贝构造函数, 拷贝赋值运算符 ...
- 【C++】《C++ Primer 》第十三章
第十三章 拷贝控制 定义一个类时,需要显式或隐式地指定在此类型地对象拷贝.移动.赋值和销毁时做什么. 一个类通过定义五种特殊的成员函数来控制这些操作.即拷贝构造函数(copy constructor) ...
随机推荐
- arm-linux-gcc-4.3.2安装步骤
安装交叉编译工具链: 1.首先以root用户登入 2.复制arm-linux-gcc-4.3.2.tgz到根目录下tmp文件夹里 3.解压命令tar xvzf arm-linux-gcc-4. ...
- 7 libjpeg使用
一.交叉编译libjepg编译 tar xzf libjpeg-turbo-1.2.1.tar.gz ./configure –help ./configure --prefix=/work/proj ...
- socket编程概述
一.基本流程如下: 整个TCP过程非常类似于电话系统.TCP是有两个不同主机上的进程需要进行通信,电话系统是有两个人位于不同地区的人需要进行通信. socket:获得可用于进程通信的端点.由于Linu ...
- scst使用的一些问题
1,编译问题 问题描述: [root@localhost scstadmin]# make cd scstadmin && make all ]: Entering directory ...
- 解决C#的64位打包程序,在64位机器上运行出现BadImageFormatException异常。
转载自:http://msdn.microsoft.com/zh-cn/library/system.badimageformatexception%28v=vs.100%29.aspx BadIma ...
- FastReport产品介绍及免费下载地址
公司地址: 俄罗斯 公司网址: http://www.fast-report.com 详细信息: 由技术总监Alexander Tzyganenko创建于1998年,Fast Reports, Inc ...
- 0816 1459 json & pickle ,目录导入,目录规范
---恢复内容开始--- 1.json & pickle 磁盘上只能存储字符串或二进制数据,直接存字典.列表.元组等是存不了的,所以需要把各种数据转换成字符串格式,然后再存到硬盘. 直接将一个 ...
- windows-ubuntu环境变量的设置格式的不同
1 在Ubuntu下输出环境变量,比如JAVA_HOME, 使用cat或者echo $JAVA_HOME即可,但是在windows下不可以, windows不支持cat命令,只能使用echo %JA ...
- 实现:TextView自由复制功能
源代码已经上传,链接地址:http://download.csdn.net/detail/huangyabin001/7556825 点击打开链接 package com.example.copyfr ...
- JAVA的第一个程序
关于下载安装环境变量的配置网上的教程已经很多了 . 下面附上第一个HelloWorld的代码 /** 这是我的第一个java程序 */ public class HelloWorld { public ...