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) ...
随机推荐
- Spring学习笔记之Constructor-based or setter-based DI?
如果是强制依赖,那么使用构造器注入,如果是可选依赖,那么使用set方法注入.Spring鼓励构造器注入,可以确保依赖项不为null, Since you can mix constructor-bas ...
- URL动态赋值
url动态赋值: 指的是url中包含{selector},即花括号括起来的jQuery选择器,当提交该url时,框架会自动将selector对应元素的值替换到花括号所占区域. (感觉实现了一点类似el ...
- poj1845 数论
//Accepted 204K 16MS //约数和 //n=p1^e1*p2^e2***pk^ek //约数和为:(p1^0+p1^1+..+p1^e1)*(p2^0+p2^1+..+p2^e2)* ...
- 45个非常有用的oracle语句(摘自尚学堂)
日期/时间 相关查询 获取当前月份的第一天 运行这个命令能快速返回当前月份的第一天.你可以用任何的日期值替换 “SYSDATE”来指定查询的日期. 1 2 SELECT TRUNC (SYSDATE, ...
- 5分钟学习maven
英文原地址:http://maven.apache.org/guides/getting-started/maven-in-five-minutes.html 一.前提 需要懂得如何在计算机上安装软件 ...
- 使用copy来拷贝对象
拷贝对象 您通过将 copy 消息发送给对象,以制作对象的副本. NSArray *myArray = [yourArray copy]; 要拷贝,接收对象的类必须遵守 NSCopying 协议.如果 ...
- android中string.xml引起的常见编译错误
1.遇到如下错误的时候说明你需要在单引号签名加转义字符(\): 1 Description Resource Path Location Type error: Apostrophe not prec ...
- CoreData的使用入门到精通
源码下载地址: http://download.csdn.net/download/huntaiji/6664567 一,创建项目文件--选择Empty Application 起名:CoreDat ...
- 20145210 《Java程序设计》第09周学习总结
教材学习内容总结 第十六章 整合数据库 •JDBC(Java DataBase Connectivity) •JDBC是用于执行SQL的解决方案 •JDBC全名Java DataBase Connec ...
- Java 集合深入理解(14):Map 概述
点击查看 Java 集合框架深入理解 系列, - ( ゜- ゜)つロ 乾杯~ 终于把 List 常用的几种容器介绍完了,接下来开始 Map 的相关介绍. 什么是 Map Java 中的 Map 接口 ...