Effective C++

chapter 2. 构造 / 析构 / 赋值运算

(Constructors, Destructors, and Assignment Operators)

Item 5. 了解 C++ 默默编写并调用哪些函数

(Know what functions C++ silently writes and calls)

当 C++ 处理过一个 empty class (空类)之后,其便不再是一个 empty class。如果你自己没有声明,编译器就会为它声明(编译器版本的)一个 copy 构造函数、一个 copy assignment 操作符和一个析构函数。此外如果没有声明任何构造函数,编译器也会声明一个 default 构造函数。所有这些函数都是 public 且 inline (见 Item 30)。因此,如果写下:

class Empty { };

这就好像写下这样的代码:

class Empty
{
public:
Empty() { ... } // default 构造函数
Empty(const Empty& rhs) { ... } // copy 构造函数
~Empty() { ... } //析构函数,是否该是 virtual 见稍后说明
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(见 Item 7),除非这个 class 的 base class 自身声明有 virtual 析构函数(这种情况下这个函数的虚属性主要来自 base class)。

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

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

由于其中声明了一个构造函数,编译器于是不再为它创建 default 构造函数。这意味着你用心设计一个 class,其构造函数要求实参,你就无需担心编译器会毫无挂虑地为你添加一个无实参构造函数(即 default 构造函数)而遮盖掉你的版本。

NamedObject 既没有声明 copy 构造函数,也没有声明 copy assignment 操作符,所以编译器会为它创建那些函数(如果它们被调用的话)。现在,看看 copy 构造函数的用法:

NamedObject<int> no1("Smallest Prime Number", 2);
NamedObject<int> 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 (因为对此 template 具现体而言 T 是 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:
// 以下构造函数如今不再接受一个 const 名称,因为 nameValue 如今是个 reference-to-non-const string。
// 先前那个 char* 构造函数已经过去了,因为必须有个 string 可供指涉。
NamedObject(std::string& name, const T& value);
... // 如前,假设并未声明 operator=
private:
std::string& nameValue; //这如今是个 reference
const T objectValue; // 这如今是个 const
};

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

std::string newDog("Persephone");
std::string oldDog("Satch");
NamedObject<int> p(newDog, 2);
NamedObject<int> s(oldDog, 36);
p = s; // 现在 p 的成员变量该发生什么事?

赋值之前,不论 p.nameValue 和 s.nameValue 都指向 string 对象(当然不是同一个)。赋值之后 p.nameValue 应该指向 s.nameValue 所指向的那个 string 吗?也就是说 reference 自身可被改动吗?如果是,那可就开辟了新天地,因为 C++ 并不允许“让 reference 改指向不同对象”。换一个想法,p.nameValue 所指向的那个 string 对象该被修改,进而影响“持有 pointers 或 references 而且指向该 string”的其他对象吗?也就是对象不被直接牵扯到赋值操作内?编译器生成的 copy assignment 操作符究竟该怎么做呢?

面对这个难题,C++ 的响应是拒绝编译那一行赋值动作。如果你打算在一个“内含 reference 成员”的 class 内支持赋值操作(assignment),你必须自己定义 copy assignment 操作符。面对“内含 const 成员”(如本例的 objectValue)的 classes,编译器的反应也是一样。更改 const 成员是不合法的,所有编译器不知道如何在它自己生成的赋值函数内面对它们。最后还有一种情况:如果某个 base classes 将 copy assignment 操作符申明为 private,编译器将拒绝为其 derived classes 生成一个 copy assignment 操作符。毕竟编译器为 derived classes 所生的 copy assignment 操作符想象中可以处理 base class 成分(见 Item 12),但它们当然无法调用 derived class 无权调用的成员函数。编译器无能无力。

请记住:

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

Effective C++ 之 Item 5:了解C++默默编写并调用哪些函数的更多相关文章

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

    class Empty{}; class Empty{ Empty(){}; Empty(const Empty& rhs){}; ~Empty(){}; Empty& operato ...

  2. EC笔记,第二部分:5.了解C++默默编写并调用哪些函数

    5.了解C++默默编写并调用哪些函数 1.C++空类 C++会为一个空类建立以下函数 (1).默认构造函数 (2).默认拷贝构造函数 (3).析构函数 (4).赋值运算符(如果成员包含引用类型或con ...

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

    面对“内含reference成员或者含const成员”的class内支持赋值操作,你必须自己定义copy assignment操作符. 如果某个base classes将copy assignment ...

  4. [Effective C++ --005]了解C++默默编写并调用哪些函数

    <前言>编译器是个十分敬业的工作者,不但为你编译代码,甚至为你生成代码,不可思议吧.本文主要介绍编译器究竟会为我们生成和调用哪些代码. <空类和非空类>如果问什么样的类是空类? ...

  5. effective c++(05)(06)之c++默默编写并调用的函数

    1. 当只写一个空类的时候,编译器会为他声明一个copy构造函数,一个copy assignment函数和一个析构函数.如下: 如果写下: class Empty{ }; 编译器就会实现以下代码: c ...

  6. Effective C++ 条款五 了解C++默默编写并调用哪些函数

      //申明一个类时,编译器会默认为你提供四个函数. //无参构造函数,析构函数,copy构造函数,copy assignment操作符.     template <typename T> ...

  7. 了解 C++ 默默编写并调用的函数

    前言 对于一个类来说,最最基础的三类成员函数莫过于:构造函数,析构函数以及拷贝函数 (copy构造函数和=重载函数).即使你的类没有为这些函数做出定义,C++ 也会自动为你创建.本文将讲述的是 C++ ...

  8. C++编译器默默编写并调用哪些函数

    什么时候empty class(空类)不再是个empty class呢?当C++处理过它之后,是的,如果你自己没有声明,编译器就会为它声明(编译器版本)一个copy构造函数.一个copy assign ...

  9. 【05】了解C++默默编写并调用那些函数

    1.如果没有声明copy构造方法,copy赋值操作符,和析构方法,编译器会自动生成这些方法,且是inline. 2.如果没有声明任何构造方法,编译器会自动生成一个default构造方法,且是inlin ...

随机推荐

  1. 【GoLang】GoLang 错误处理 -- 使用异常的思路进行处理

    go处理错误的另一种方式 go处理错误常见的方式是 err := funcReturningError() if err != nil { // 处理错误 } 然而因为过于繁琐而饱受诟病.下文简述另一 ...

  2. css3创建3D场景

    浏览器本身是一个2维平面,对于3D的情况,实际上是增加了一个维度(深度),所以我们需要创建一个3D场景.这时浏览器不仅仅是一个平面了,更像是一个窗口,我们透过这个窗口去观察里面的三维世界.所谓的创建3 ...

  3. Java Statement和PreparedStatement性能测试(转)

    本文转载自http://blog.csdn.net/liubo5005/article/details/7239935 先上代码: import java.sql.Connection; import ...

  4. javax.imageio.IIOException: Can't create cache file!

    javax.imageio.IIOException: Can't create cache file! at javax.imageio.ImageIO.createImageInputStream ...

  5. linux skill

    linux console终端乱码解决 1.console终端乱码 在/etc/profile文件的最后一行添加如下内容: export LC_ALL="zh_CN.GB18030" ...

  6. ansible操作远程服务器报Error: ansible requires the stdlib json or simplejson module, neither was found!

    通过ansible执行远程命令时报如下异常: Error: ansible requires the stdlib json or simplejson module, neither was fou ...

  7. radio选中

    设置选中:$(':radio[name=isnode][value=' + isnode + ']').prop('checked',true); 1.获取选中值,三种方法都可以: $('input: ...

  8. 【leetcode】Combinations (middle)

    Given two integers n and k, return all possible combinations of k numbers out of 1 ... n. For exampl ...

  9. (2016弱校联盟十一专场10.2) A.Nearest Neighbor Search

    题目链接 水题,算一下就行. #include <bits/stdc++.h> using namespace std; typedef long long ll; ll x[],y[], ...

  10. Mysql 死锁的详细分析方法

    用数据库的时候,偶尔会出现死锁,针对我们的业务系统,出现死锁的直接结果就是系统卡顿.客户找事儿,所以我们也在想尽全力的消除掉数据库的死锁.出现死锁的时候,如果只是想解锁,用show full proc ...