Effective C++——条款5(第2章)

第2章    构造/析构/赋值运算

Constructors,Destructors,and Assignment Operator

几乎每一个 class 都会有一个或多个构造函数,一个析构函数,一个copy assignment 操作符.

条款05:    了解C++默默编写并调用哪些函数

Know what functions C++ silently writes and calls

什么时候empty class 不再是个empty class 呢?C++处理过它之后.如果自己没有声明,编译器就会为它声明一个copy构造函数,一个copy assignment操作符和一个析构函数.此外如果没有声明任何构造函数,编译器也会声明一个default
构造函数.所有这些函数都是 public 且 inline.
因此,如果写下:

class Empty {};

就好像写下这样的代码:

class Empty {
public:
    Empty() { ... }                                // default构造函数
    Empty(const Empty &rhs) { ... }                // copy构造函数
    ~Empty() { ... }                               // 析构函数
    Empty& operator=(const Empty &rhs) { ... }     // copy assignment操作符
};

唯有这些函数被调用,它们才会被编译器创建出来.程序中需要它们是很平常的事.下面代码造成上述每一个函数被编译器产出:

Empty e1;            // default构造函数
Empty e2(e1);        // copy构造函数
e2 = e1;             // copy assignment操作符

编译器编写这些函数,这些函数做了什么呢?default 构造函数和析构函数主要是给编译器一个地方用来放置"藏身幕后"的代码,像是调用base classes 和non-static 成员变量的构造函数和析构函数.编译器产生的析构函数是个non-virtual,除非这个 class 的base class 自身声明有 virtual 析构函数.

至于copy构造函数和copy assignement操作符,编译器创建的版本只是单纯地将来源对象的每一个non-static 成员变量拷贝到目标对象.考虑一个NamedObject template,它允许将一个个名称和类型为T的对象产生关联:

templat <typename T>
class NamedObject {
public:
    NamedObject(const char *name, const T &value);
    NamedObject(const std::string &name, const T &value);
private:
    std::string nameValue;
    T objectValue;
};

由于其中声明一个构造函数,编译器于是不再为它创建 default 构造函数.这意味着如果设计一个 class,其构造函数要求实参,就无须担心编译器会毫无挂虑的添加一个无实参构造函数而遮掉自己的版本.

NamedObject即没有声明copy构造函数,也没有声明copy assignment操作符,所以编译器会为它创建那些函数.现在看看copy构造函数的用法:

NamedObject<int> no1("Smallest Prime Number", 2);
NamedObject no2(no1);                            // 调用copy构造函数

编译器生成的copy构造函数必须以no1.nameValue和no1.objectValue为初值设定no2.nameValue和no2.objectValue.两者中,nameValue的类型是string,而标准string有个copy构造函数,所以no2.nameValue的初始化方式是调用string的copy构造函数并以no1.nameValue为实参.另一个成员NamedObject<int>::objectValue的类型是int,那是个内置类型,所以no2.objectValue会以"拷贝no1.objectValue内的每一个bits"来完成初始化.

编译器为NamedObject<int>所生成的copy assignment操作符,其行为基本上与copy构造函数一样,但一般而言只有当生出的代码合法且有适当机会证明它有意义,其表现才会如前所述.如果有条件不符合,编译器就拒绝为 class 生成 operator=.

例如,NamedObject定义如下,其中nameValue是个reference to string,objectValue是个 const T:

template <class T>
class NamedObject {
public:
    NamedObject(std::string& name, const T& value);
private:
    std::string& nameValue;
    const T objectValue;
};

现在考虑下面会发生什么事:

std::string newDog("Persephone");
std::string oldDog("Satch");
NamedObject<int> p(newDog, 2);
NamedObject<int> s(odlDog, 36);
p = s;

赋值之前,不论p.nameValue和s.nameValue都指向string对象.赋值动作该如何影响p.nameValue呢?赋值之后p.nameValue应该指向s.nameValue所指的那个string吗?

面对这个难题,C++的回应是拒绝编译那一行赋值动作.如果打算在一个"内含reference成员"的 class 内支持赋值操作,必须自定义copy assignment操作符.面对"内含const成员"的classes,编译器的反应也一样.

注意:

编译器可以暗自为 class 创建 default 构造函数,copy构造函数,copy assignment操作符,以及析构函数.

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-12-17 19:30:28

Effective C++——条款5(第2章)的相关文章

Effective C++——条款13(第3章)

第3章    资源管理 Resource Management 所谓资源就是,一旦用了它,将来必须还给系统.C++程序中最常使用的资源就是动态内存分配(如果分配内存从来都增归还,会导致内存泄露).其他常见的资源还有文件描述符(file descriptors),互斥锁(mutex locks),图形界面中的字型和笔刷,数据库连接,以及网络sockets.不论哪一种资源,重要的是,不再使用它时,必须将它还给系统. 条款13:    以对象管理资源 Use objects to manage res

Effective C++——条款15(第3章)

条款15:    在资源管理类中提供对原始资源的访问 Provide access to raw resources in resources-managing classes 资源管理类(resource-managing classes)很棒.它们是对抗资源泄露的堡垒.在一个良好的环境中将依赖这样的classes来处理和资源之间的所有互动.而不是直接处理原始资源,但这个环境并不完美,许多API直接涉及资源,因此有时只能绕过资源管理对象直接访问原始资源(raw resources). 例如,条

Effective C++——条款14(第3章)

条款14:    在资源管理类中小心copying行为 Think carefully about copying behavior in resource-managing classes 条款13导入这样的观念:"资源取得时机便是初始化时机"(Resource Acquisition Is Initializaiton,RAII),并以此作为"资源管理类"的脊柱,也描述了auto_ptr和tr1::shared_ptr如何将这个观念表现在heap-based资源

Effective C++——条款9(第2章)

条款09:    绝不在构造和析构过程中调用 virtual 函数 Never call virtual functions during construction or destruction 不应该在构造函数和析构函数期间调用 virtual 函数,因为这样的调用不会带来预想的结果. 假设有个 class 继承体系,用来模塑股市交易如买进,卖出的订单等等.这样的交易一定要经过审计,所以每当创建一个交易对象,在审计日志中也需要创建一笔适当记录.下面是一个看起来颇为合理的做法: class Tr

Effective C++——条款7(第2章)

条款07:    为多态基类声明 virtual 析构函数 Declare destructors virtual in polymorphic base classes 设计以下时间基类TimeKeeper: class TimeKeeper { public: TimeKeeper(); ~TimeKeeper(); }; class AtomicClock : public TimeKeeper { ... }; class WaterClock : public TimeKeeper {

Effective C++——条款6(第2章)

条款06:    若不想使用编译器自动生成的函数,就该明确拒绝 Explicitly disallow the use of compiler-generated functions you do not want. 在某些情况下,希望保持对象的唯一性,不想让对象有其他副本.如下: class HomeForSale { ... }; HomeForSale h1; HomeForSale h2; HomeForSale h3(h1); // 企图拷贝h1,不应该通过编译 h1 = h2; //

More Effective C++ 条款35 让自己习惯于标准C++ 语言

(由于本书出版于1996年,因此当时的新特性现在来说可能已经习以为常,但现在重新了解反而会起到了解C++变迁的作用) 1. 1990年后C++的重要改变 1). 增加了新的语言特性:RTTI,namespaces,bool,关键词mutable和explicit,enums作为重载函数之自变量所引发的类型晋升转换,以及"在class 定义区内直接为整数型(intergral) const static class members设定初值"的能力. 2). 扩充了Templates的特性

effective c++ 条款4 make sure that objects are initialized before they are used

1 c++ 类的数据成员的初始化发生在构造函数前 class InitialData { public: int data1; int data2; InitialData(int a, int b) { data1 = a: //this is assignment data2 = b; //this is assignment } /* InitialData(int a, int b):data1(a),data2(b) //this is initial {} */ } 2 不同cpp文

More Effective C++ 条款34 如何在一个程序中结合C++和C

1. C++和C混合使用的前提之一就是编译器产生兼容的目标文件(.lib和.dll等).所谓"兼容",指的是编译器在"预编译器相依的特性上"一致,如int和double大小,参数压栈机制等,只有在这个基础上才能讨论结合使用C++和C模块的问题. 2. 在1的基础上,要结合使用C++和C的模块,主要有以下几点需要注意: 1). name mangling(名称重整) Name mangling是C++用于支持函数重载的机制,它对重载的函数名称进行一定改变,使得每个函数