读书笔记 effective c++ Item 5 了解c++默认生成并调用的函数
1 编译器会默认生成哪些函数
什么时候空类不再是一个空类?答案是用c++处理的空类。如果你自己不声明,编译器会为你声明它们自己版本的拷贝构造函数,拷贝赋值运算符和析构函数,如果你一个构造函数都没有声明,编译器同样会为你声明一个默认拷贝构造函数。这些所有的函数会是public和inline的(Item30)。因此,如果你写了下面的类:
1 class Empty{};
本质上来说和下面的类是一样的:
1 class Empty { 2 3 public: 4 5 Empty() { ... } // default constructor 6 7 Empty(const Empty& rhs) { ... } // copy constructor 8 9 ~Empty() { ... } // destructor — see below 10 11 // for whether it’s virtual 12 13 Empty& operator=(const Empty& rhs) { ... } // copy assignment operator 14 15 };
这些函数只有在被需要的时候才会生成。程序中需要这些函数是很平常的事。下面的代码会导致相应的函数被生成:
1 Empty e1; // default constructor; destructor 2 3 Empty e2(e1); // copy constructor 4 5 e2 = e1; // copy assignment operator
2 默认生成的函数会做些什么?
考虑到编译器会为你生成函数,这些函数会做些什么?默认拷贝构造函数和析构函数会给编译器腾出一个地方用来放“藏在幕后的代码”,像基类和非静态数据成员的构造函数和析构函数的调用。注意生成的默认析构函数不是虚函数(Item7),除非生成默认析构函数的类继承自一个声明了虚析构函数的基类(这样默认析构函数的虚或者非虚继承自基类)。
2.1 默认拷贝构造函数
对于拷贝构造函数和拷贝赋值运算符来说,编译器生成的版本只是将源对象的非静态数据成员简单的拷贝到目标对象上。例如,考虑一个NamedObject 模板类允许你同类型T的对象进行关联。
1 template<typename T> 2 3 class NamedObject { 4 5 public: 6 7 NamedObject(const char *name, const T& value); 8 9 NamedObject(const std::string& name, const T& value); 10 11 ... 12 13 private: 14 15 std::string nameValue; 16 17 T objectValue; 18 19 };
因为在NamedObject中声明了一个构造函数,编译器不再为其生成默认构造函数。这很重要,如果你仔细的设计了一个需要带参构造函数的类,你不必担心编译器会添加一个不带参数的构造函数来覆盖你的版本。
NamedObject既没有声明拷贝构造函数也没有声明拷贝赋值运算符,所以编译器会产生这些函数(如果它们被需要)。看下面的例子,拷贝构造函数这么使用:
1 NamedObject<int> no1("Smallest Prime Number", 2); 2 3 NamedObject<int> no2(no1); // calls copy constructor
编译器生成的拷贝构造函数必须分别用no1.nameValue和no1.objectValue初始化no2.nameValue和no2.objectValue。nameValue的类型是string,标准的string类型有个拷贝构造函数,于是no2.nameValue会通过调用string的拷贝构造函数来进行初始化,构造函数用no1.nameValue作为参数。另外,NamedObject<int>::objectValue的类型是int,于是no2.objectValue会通过拷贝no1.objectValue的bits来进行初始化。
2.2 默认拷贝赋值运算符
编译器为NamedObject<int>生成的拷贝赋值运算符的行为同拷贝构造函数基本上是一样的。但是一般来说,编译器生成的拷贝赋值运算符只有在生成的代码合法,并且有合理的机会证明其有意义,行为同拷贝构造函数才是一样的。如果不满上述任何一个条件,编译器都会拒绝为你的类生成operator=。
举个例子:考虑NamedObject像下面这样定义,nameValue是指向string的引用,objectValue是const T.
1 template<typename T> 2 3 class NamedObject { 4 5 public: 6 7 // this ctor no longer takes a const name, because nameValue 8 9 // is now a reference-to-non-const string. The char* constructor 10 11 // is gone, because we must have a string to refer to. 12 13 NamedObject(std::string& name, const T& value); 14 15 ... // as above, assume no 16 17 // operator= is declared 18 19 private: 20 21 std::string& nameValue; // this is now a reference 22 23 const T objectValue; // this is now const 24 25 };
考虑下面会发生什么:
1 std::string newDog("Persephone"); 2 3 std::string oldDog("Satch"); 4 5 NamedObject<int> p(newDog, 2); // when I originally wrote this, our 6 7 // dog Persephone was about to 8 9 // have her second birthday 10 11 NamedObject<int> s(oldDog, 36); // the family dog Satch (from my 12 13 // childhood) would be 36 if she 14 15 // were still alive 16 17 p = s; // what should happen to the data members in p?
在赋值之前,p.nameValue和s.nameValue都会指向string对象,虽然不是同一个。赋值如何影响p.nameValue?在赋值之后,p.nameValue应该指向s.nameValue所指向的那个string么?也就是,引用本身会改变么?如果是这样,就开辟了一块新天地,因为c++没有提供使引用指向另一个对象的方法。或者,p.nameValue指向的string对象应该被修改?这样就会影响其他对象,这些对象持有指向这个被修改的string的指针或者引用。这是编译器生成的拷贝赋值运算符应该做得?
面对这个难题,c++拒绝编译代码。如果你想在一个类中支持含有引用成员的拷贝赋值运算符,你必须自己定义一个拷贝赋值运算符。对于包含const成员的类,编译器的行为也是类似的。修改const成员是不合法的,因此当一个默认拷贝赋值运算符生成时,编译器对如何处理它们是不确定的。最后,如果在基类中将拷贝赋值运算符声明成private,编译器拒绝在派生类中生成拷贝赋值运算符。毕竟编译器为派生类生成的拷贝赋值运算符需要能够处理基类的部分,在这种情况下,它们当然不能够触发派生类没有权限调用的函数。
读书笔记 effective c++ Item 5 了解c++默认生成并调用的函数的更多相关文章
- 读书笔记 effective c++ Item 37 永远不要重新定义继承而来的函数默认参数值
从一开始就让我们简化这次的讨论.你有两类你能够继承的函数:虚函数和非虚函数.然而,重新定义一个非虚函数总是错误的(Item 36),所以我们可以安全的把这个条款的讨论限定在继承带默认参数值的虚函数上. ...
- 读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数
关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为 ...
- 读书笔记 effective c++ Item 12 拷贝对象的所有部分
1.默认构造函数介绍 在设计良好的面向对象系统中,会将对象的内部进行封装,只有两个函数可以拷贝对象:这两个函数分别叫做拷贝构造函数和拷贝赋值运算符.我们把这两个函数统一叫做拷贝函数.从Item5中,我 ...
- 读书笔记 effective c++ Item 13 用对象来管理资源
1.不要手动释放从函数返回的堆资源 假设你正在处理一个模拟Investment的程序库,不同的Investmetn类型从Investment基类继承而来, class Investment { ... ...
- 读书笔记 effective c++ Item 14 对资源管理类的拷贝行为要谨慎
1. 自己实现一个资源管理类 Item 13中介绍了 “资源获取之时也是初始化之时(RAII)”的概念,这个概念被当作资源管理类的“脊柱“,也描述了auto_ptr和tr1::shared_ptr是如 ...
- 读书笔记 effective c++ Item 15 在资源管理类中提供对原生(raw)资源的访问
1.为什么需要访问资源管理类中的原生资源 资源管理类是很奇妙的.它们是防止资源泄漏的堡垒,没有资源泄漏发生是设计良好的系统的一个基本特征.在一个完美的世界中,你需要依赖这样的类来同资源进行交互,绝不 ...
- 读书笔记 effective c++ Item 17 使用单独语句将new出来的对象放入智能指针
1. 可能会出现资源泄漏的一种用法 假设我们有一个获取进程优先权的函数,还有一个在动态分类的Widget对象上根据进程优先权进行一些操作的函数: int priority(); void proces ...
- 读书笔记 effective c++ Item 24 如果函数的所有参数都需要类型转换,将其声明成非成员函数
1. 将需要隐式类型转换的函数声明为成员函数会出现问题 使类支持隐式转换是一个坏的想法.当然也有例外的情况,最常见的一个例子就是数值类型.举个例子,如果你设计一个表示有理数的类,允许从整型到有理数的隐 ...
- 读书笔记 effective c++ Item 25 实现一个不抛出异常的swap
1. swap如此重要 Swap是一个非常有趣的函数,最初作为STL的一部分来介绍,它已然变成了异常安全编程的中流砥柱(Item 29),也是在拷贝中应对自我赋值的一种普通机制(Item 11).Sw ...
随机推荐
- js获取光标位置
js获取光标位置 var TT = { /* * 获取光标位置 * @Method getCursorPosition * @param t element * @return number */ ...
- android 点滴记录
1.AndroidM环境下,在framework层添加代码会对jar包的package name进行检查,并提示”unknown package name of class file”怎么解决? 产生 ...
- LPC1788的spi使用
#ifndef __SPI_H_ #define __SPI_H_ #include "common.h" #include "delay.h" // cs p ...
- listen函数里面backlog的意义以及各种情况
先看了这篇: http://www.cppblog.com/thisisbin/archive/2010/02/07/107444.html 里面说了会维护两个队列,established 和 syn ...
- Tsinsen-A1489 抽奖 【数学期望】
乔明达太神,其实已经题解非常清楚了,我再推一遍吧. 题目意思相当于有n个盒子,无差别投m次球,每个盒子的得分为每个盒子里的球的个数. 第一问: 假设这个球放在了第i个盒子里,那么 ∆ans = (mi ...
- 处理JSON格式的数据
JSON格式的数据是最常用的数据格式,处理方法的选择就显得比较重要了.我常用的一种是用对象来接收,然后保存在数组中,需要时直接从数组中取值.下面列出一个小例子. .h文件中: #import < ...
- 简易的AJAX工具[转]
关键字: ajax 1.创建XMLHttpRequest对象的js文件 Ajax.js function Ajax(){ var xmlHttp=null; if(window.XMLHt ...
- xamarin mac 之 资料
中文社区 xamarin.form 30天学习计划
- WebGIS中通过行列号来换算出多种瓦片的URL 之离线地图(转载)
WebGIS中通过行列号来换算出多种瓦片的URL 之离线地图 1.前言 在前面我花了两个篇幅来讲解行列号的获取,也解释了为什么要获取行列号.在这一章,我将把常见的几种请求瓦片时的URL样式罗列出来,并 ...
- 11.TCP的交互数据流
TCP报文段一般有两类,分别是成块数据和交互数据. 1.交互式输入 Rlogin连接上键入一个交互命令的数据流如下图所示. 每一个交互按键都会产生一个数据分组,每次从客户传 ...