Effective C++: 02构造、析构、赋值运算
05:了解C++默默编写并调用哪些函数
1:一个空类,如果你自己没声明,编译器就会为它声明(编译器版本的)一个copy构造函数、一个copy assignment操作符和一个析构函数。此外如果你没有声明任何构造函数,编译器也会为你声明一个default构造函数。所有这些函数都是public且inline的。
2:只有当这些函数被调用时,它们才会被编译器创建出来。
3:编译器生成的default构造函数和析构函数主要是给编译器一个地方用来放置“藏身幕后”的代码,比如用base classes和non-static成员变量的构造函数和析构函数。至于copy构造函数和copy assignment操作符,编译器创建的版本只是单纯的将来源对象的每一个non-static成员变量拷贝到目标对象。
4:编译器创建的析构函数是个non-virtual,除非这个class的base class自身声明有virtual析构函数。
5:以下情况下,编译器会拒绝为class生出operator=:
a、类中具有引用成员或者const 常量成员:
template<class T>
class NamedObject {
public:
NamedObject(std::string& name, const T& value);
...
private:
std::string& nameValue; // this is a reference
const T objectValue; // this is const
};
引用和常量必须在定义时进行初始化,不支持赋值操作。因此编译器拒绝为这样的类创建operator=函数;
如果你打算在一个包含reference成员或const成员的class内支持赋值操作,你必须自己定义copy assignment操作符。
b、如果某个base class将copy assignment操作符声明为private,编译器也拒绝为其derived class生成一个copy assignment操作符。
06:若不想使用编译器自动生成的函数,就应该明确拒绝
1:如果不希望class支持复制初始化或者赋值操作,因为不定义copy构造函数和copy assignment操作符,编译器会自动创建一个,因此不定义这俩函数达不到这个目的。
2:因为编译器创建的函数都是public的,为了阻止这些函数被创建出来,可以将copy构造函数或copy assignment操作符声明为private。这样便阻止了编译器创建这些函数,而且类的用户也无法调用它们。
3:上面的做法还是有漏洞,因为类的成员函数和友元函数还是可以调用你的private函数。这种情况下,可以仅仅声明而不去定义它们。这种情况下,如果有成员函数或友元函数调用它们的话,将会产生一个连接错误。
因此,将复制构造函数和赋值操作符声明为private且不去定义它们,当类的用户企图拷贝时,编译器会阻止他;如果在成员函数或友元函数中这么做,连接器会发出抱怨。
4:将连接期错误移至编译期是可能的(而且那时好事,毕竟越早侦测出错误越好),只要定义一个Uncopyable类,并将自己的类继承该类就好:
class Uncopyable {
protected: // allow construction
Uncopyable() {} // and destruction of
~Uncopyable() {} // derived objects... private:
Uncopyable(const Uncopyable&); // ...but prevent copying
Uncopyable& operator=(const Uncopyable&);
};
任何人(包括成员函数或友元函数)尝试拷贝Uncopyable类的派生类对象时,编译期便试着生成一个copy构造函数和一个copy assignment操作符。这些函数的编译器生成版会尝试调用其base class的对应函数,那些调用会被编译器拒绝,因为其base class的拷贝函数是private。
07:为多态基类声明virtual析构函数
1:当derived class对象经由一个base class指针删除,而该base class带着一个non-virtual析构函数,则其结果是未定义的。实际执行时通常发生的是对象的derived成分没被销毁,而其base class成分通常会被销毁,于是造成一个诡异的“局部销毁”对象。
2:任何class只要带有virtual函数都几乎确定应该也有一个virtual析构函数。
3:如果class不含virtual函数,通常表示它并不愿意被用做一个base class。当class不企图被当作base class时,令其析构函数为virtual往往是个馒主意。
欲实现出virtual函数,对象必须携带某些信息,主要用来在在运行期决定哪一个virtual函数该被调用。这份信息通常是由一个所谓vptr ( virtual table pointer)指针指出。vptr指向一个由函数指针构成的数组,称为vtbl ( virtual table );每一个带有virtual函数的class都有一个相应的vtbl。当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr所指的那个vtb----编译器在其中寻找适当的函数指针。
因此,无端的将某个class的析构函数声明为virtual,会增加对象的体积(vptr)。因此许多人的心得是:只有当class内含至少一个virtual函数,才为它声明virtual析构函数。
4:标准string,以及所有STL容器如vector,list,set,map等等,都不virtual析构函数,因此,不应该将它们当做base class。
5:如果抽象基类声明了virtual析构函数,则必须为它提供一份定义:
class AWOV {
public:
virtual ~AWOV() = ; // declare pure virtual destructor
}; AWOV::~AWOV() {} // definition of pure virtual dtor
这是因为,派生类继承该抽象基类后,派生类对象销毁时,会首先调用派生类的析构函数,然后是基类的析构函数。因此编译器会在AWOV的derived classes的析构函数中创建一个对~AWOV的调用,所以必须提供一份定义,否则会连接错误。
08:别让异常逃离析构函数
C++并不禁止析构函数吐出异常,但它不鼓励这么做。如果某个类的析构函数有可能抛出异常,则要么:抛出异常时直接调用abort退出程序;要么抛出异常时吞下异常,仅记录日志。
09:绝不在构造和析构过程中调用virtual函数
1:不要再构造函数和析构函数中,调用virtual函数。
2:在base class构造期间,如果构造函数中调用了virtual函数,即使当前正在构造derived class(构造派生类对象时,需要首先构造其基类部分),virtual函数也是base class中的版本。也就是说;在base class构造期间,virtual函数不是virtual函数。
在derived class对象的base class构造期间,对象的类型是base class而不是derived class。不只virtual函数会被编译器解析至(resolve to)base class,若使用运行期类型信息(runtime type information,例如dynamic_cast和typeid),也会把对象视为base class类型。
3:相同道理也适用于析构函数。一旦derived class析构函数开始执行,对象内的derived class成员变量便呈现未定义值,所以C++视它们仿佛不再存在。进入base class析构函数后对象就成为一个base class对象。
4:确定你的构造函数和析构函数都没有调用virtual函数,而它们调用的所有函数也都要服从这一约束。
10:令operator=返回一个reference to *this
1:赋值时,可以将其写成连锁形式:x = y = z = 15;赋值采用右结合律,因此这个表达式等价于:x = ( y = ( z = 15 ) );
2:为了实现“连锁赋值”,赋值操作符必须返回一个reference指向操作符的左侧实参。这是你为classes实现赋值操作符时应该遵循的协议:
Widget& operator=(const Widget& rhs) // return type is a reference to
{ // the current class
...
return *this; // return the left-hand object
}
3:这个协议不仅适用于标准的赋值形式,也适用于所有赋值相关运算,比如+=。
11:在operator=中处理“自我赋值”
1:“自我赋值”发生在对象被赋值给自己时,不要认定客户绝不会那么做,而且自我赋值并不总是那么可被一眼辨识出来,例如:a[i] = a[j];这条语句中,如果i和j相同,这便是自我赋值;再比如:*px = *py;如果px和py恰巧指向相同,这也是自我赋值。
2:“自我赋值”时,可能会掉进“在停止使用资源之前意外释放了它”的陷阱。比如:
Widget& Widget::operator=(const Widget& rhs)
{
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
这里的自我赋值问题是,operator=函数内的*this和rhs有可能是同一个对象。果真如此delete就不只是销毁当前对象的bitmap,它也销毁rhs的bitmap。
欲阻止这种错误,传统做法是藉由operator=最前面的一个“证同测试(identity test )”达到“自我赋值”的检验目的:
Widget& Widget::operator=(const Widget& rhs)
{
if (this == &rhs) return *this; // identity test: if a self-assignment,
// do nothing
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
3:这个新版本仍然存在异常方面的麻烦。更明确地说,如果”new Bitmap”导致异常(不论是因为分配时内存不足或因为Bitmap的copy构造函数抛出异常),Widget最终会持有一个指针指向一块被删除的Bitmap。
令人高兴的是,让operator=具备“异常安全性”往往自动获得“自我赋值安全”的回报。因此愈来愈多人对“自我赋值”的处理态度是倾向不去管它,把焦点放在实现“异常安全性”上。例如,我们只需注意在复制pb所指东西之前别删除pb:
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap *pOrig = pb; // remember original pb
pb = new Bitmap(*rhs.pb); // make pb point to a copy of *pb
delete pOrig; // delete the original pb
return *this;
}
现在,如果”new Bitmap”抛出异常,pb保持原状。即使没有证同测试,这段代码还是能够处理自我赋值,因为我们对原bitmap做了一份复件、删除原bitmap、然后指向新制造的那个复件。它或许不是处理“自我赋值”的最高效办法,但它行得通。
4:在operator=函数内确保代码不但“异常安全”而且“自我赋值安全”的一个替代方案是,使用所谓的copy and swap技术。
它是一个常见而够好的operator=撰写办法:
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs); // make a copy of rhs's data swap(temp); // swap *this's data with the copy's
return *this;
}
或者,可能更常见的是下面这种写法:
Widget& Widget::operator=(Widget rhs)
{
swap(rhs);
return *this;
}
12:复制对象时勿忘其每一个成分
1:如果自己写复制构造函数或赋值操作符而不使用编译器的版本,则需要注意的是:如果你为class添加一个成员变量,你必须同时修改复制构造函数和赋值操作符函数(你也需要修改class的所有构造函数,以及任何非标准形式的operator=(比如+=))。如果你忘记,编译器不太可能提醒你。
2:任何时候只要你承担起“为derived class撰写copying函数”的重责大任,必须很小心地也复制其base class成分。那些成分往往是private,所以你应该让derived class的copying函数调用相应的base class函数。
Effective C++: 02构造、析构、赋值运算的更多相关文章
- 【Effective C++】构造/析构/赋值运算
条款05:了解C++默默编写并调用哪些函数 默认构造函数.拷贝构造函数.拷贝赋值函数.析构函数构成了一个类的脊梁,只有良好的处理这些函数的定义才能保证类的设计良好性. 当我们没有人为的定义上面的几个函 ...
- Effective C++ 2.构造 析构 赋值运算
//条款07:为多态基类声明virtual析构函数 // 1.若基类的析构函数不定义为虚函数,由于基类的指针或引用可以指向派生类的对象,则在删除基类对象的时候可能会出错,导致破坏数据结构. // 2. ...
- 《Effective C++》第2章 构造/析构/赋值运算(2)-读书笔记
章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...
- 《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记
章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...
- EffectiveC++ 第2章 构造/析构/赋值运算
我根据自己的理解,对原文的精华部分进行了提炼,并在一些难以理解的地方加上了自己的"可能比较准确"的「翻译」. Chapter 2 构造 / 析构 / 赋值 条款 05:了解C++ ...
- Effective C++ —— 构造/析构/赋值运算(二)
条款05 : 了解C++默默编写并调用哪些函数 编译器可以暗自为class创建default构造函数.copy构造函数.copy assignment操作符,以及析构函数. 1. default构造函 ...
- Effective C++ 笔记二 构造/析构/赋值运算
条款05:了解C++默默编写并调用哪些函数 编译器默认声明一个default构造函数.一个copy构造函数.一个copy assignment操作符和一个析构函数.这些函数都是public且inlin ...
- Effective C++笔记:构造/析构/赋值运算
条款05:了解C++默默编写并调用哪些函数 默认构造函数.拷贝构造函数.拷贝赋值函数.析构函数构成了一个类的脊梁,只有良好的处理这些函数的定义才能保证类的设计良好性. 当我们没有人为的定义上面的几个函 ...
- Effective C++ -- 构造析构赋值运算
05.了解C++默默编写并调用哪些函数 编译产生的析构函数时non-virtual,除非这个类的基类析构函数为virtual 成员变量中有引用和const成员时,无法自己主动生成copy assign ...
- Effective C++笔记(二):构造/析构/赋值运算
参考:http://www.cnblogs.com/ronny/p/3740926.html 条款05:了解C++默默编写并调用哪些函数 如果自定义一个空类的话,会自动生成默认构造函数.拷贝构造函数. ...
随机推荐
- php 该如何获取从百度搜索进入网站的关键词
清源分享一个php获取从百度搜索进入网站的关键词的代码,有需要的朋友可以参考一下:https://blog.csdn.net/u012275531/article/details/17609065 代 ...
- jeecms 基本架构研究
最近工作需要内容管理系统,下载了jeecms v5 顺便学习一下它的架构: 采用框架为:Hibernate3.3.2+spring3.05+springMVC+freemarker2.3.16 Hib ...
- java.lang.UnsupportedClassVersionError: com/gargoylesoftware/htmlunit/WebClient : Unsupported major.minor version 52.0 (unable to load class com.gargoylesoftware.htmlunit.WebClient)
java.lang.UnsupportedClassVersionError: com/gargoylesoftware/htmlunit/WebClient : Unsupported major. ...
- 哈哈哈哈,我竟然发现了个MSDN里面的笔误
typedef __PROCESSOR_INFO { WORD wVersion; WCHAR szProcessorCore[40]; WORD wCoreRevision; WCHAR ...
- <a>标签操作
1.点击后onclick事件失效,变灰,不可用 onclick(this); //事件传递this对象 function viewMm(obj) { $(obj).removeAttr("o ...
- Java IO:如何得到Jar包中内嵌Jar包的时间戳
ClassLoader bladeClassLoader = BladeCLI.class.getClassLoader(); URL url = bladeClassLoader.getResour ...
- Python中的进程池与线程池(包含代码)
Python中的进程池与线程池 引入进程池与线程池 使用ProcessPoolExecutor进程池,使用ThreadPoolExecutor 使用shutdown 使用submit同步调用 使用su ...
- python第一天 :计算机基础(一)
1.什么是编程语言 答:人类与计算机交流的介质 2.什么是编程 答:利用编程语言控制计算机解决问题 3.为什么要编程 答:可以控制计算机做事,提高生产生活效率 4.计算机的五大组成部分分别有什么作用? ...
- Python2.7用sys.stdout.write实现打印刷新
如何能在控制台实现在一行中显示进度的信息呢,就像使用pip安装时的进度那样. 如果用print则会打印成多行,下面这个小技巧可以在一行中打印: import time import sys if __ ...
- python学习笔记10--协程、IO、IO多路复用
本节内容 一.协程 1.1.协程概念 1.2.greenlet 1.3.Gevent 1.4.协程之爬虫 1.5.协程之socket 二.论事件驱动与异步IO 三.IO 3.1.概念说明 3.2.IO ...