读书笔记 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 ...
随机推荐
- [Unity]C#中 将XML和实体类之间进行相互转换的工具类
using System; using System.Xml; using System.Xml.Serialization; using System.IO; namespace LOTool { ...
- linux日常巡检脚本
######################以下是脚本内容开始部分###################################### #!/bin/bash #set -x2012-02-2 ...
- PC端 H5实现拍照并上传
<!DOCTYPE HTML><html><head> <meta charset="UTF-8"> <meta name=& ...
- javascript DOM(2) 一个网页上切换显示不同的图片或文本
摘自: javascript DOM 编程艺术 1. 在一个网页上切换显示不同的图片 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Tran ...
- asp.net-mvc验证码 asp.net-mvc c#验证码
验证码看不清换一组 写一个类 public class ValidateCode { public ValidateCode() { } /// <summary> /// 验证码的最大长 ...
- Jsp属性范围
IDE:MyEclipse 2014(自带Tomcat 7) Web项目路径: Web项目配置信息: WebRoot --WEB-INF --web.xml <?xml version=&quo ...
- 试水MongoDB
1)安装好后启动mongodb 服务 1_1) 建立data/db ,保证至少有3g大小的盘 1_2) 建立log 文件夹 1_3)配置文件 内容,指定数据存放位置.日志文件位置 dbpath ...
- Set笔记
Set 继承自Collection的一个接口,特点是:无序,不可重复.注意啊!!只有Collection实现了迭代器!也就是说Map是没有实现迭代器的,需要keySet,values,entrySet ...
- argparse 命令含参数模块
argparse是python的一个命令行参数模块,可以解析命令行参数,生成帮助等. 你可以这样使用它: #!/usr/bin/python from argparse import Argument ...
- Python3基础 filter()第一个参数为NONE时 结果只返回为True的对象
镇场诗: 诚听如来语,顿舍世间名与利.愿做地藏徒,广演是经阎浮提. 愿尽吾所学,成就一良心博客.愿诸后来人,重现智慧清净体.-------------------------------------- ...