• Effective C++ 学习笔记(27)


    弄清C++在幕后为你所写,所调用的函数


      一个空类什么时候不是空类?---- 当C++编译器通过它的时候。如果你没有什么下列函数,那么编译器会自动声明它自己的版本。这些函数就是:一个拷贝构造函数,一个赋值运算符,一个析构函数,一对取址运算符。如果你没有声明如何构造函数,那么它也会为你声明一个缺省构造函数。所以这些函数都是public。

    class Empty {};

      和下面是一样的:

    class Empty
    {
    public:
    Empty();

    Empty(
    const Empty & rhs);

    ~Empty();

    Empty
    & operator=(const Empty & rhs);

    Empty
    * operator&();

    const Empty * operator&() const;
    }

      现在,如果需要,这些函数就会被生成,但你会很容易就需要它们。下面的代码将使得每个函数被生成:

      

    const Empty e1; // 缺省构造函数
    // 析构函数
    Empty e2(e1); // 拷贝构造函数
    e2 = e1; // 赋值运算符
    Empty *pe2 = &e2; // 取址运算符
    // (非const)
    const Empty *pe1 = &e1; // 取址运算符
    // (const)

      假设编译器为你写了函数,这些函数又做些什么呢?是这样的,缺省构造函数和析构函数实际上什么也不做,它们只是让你能够创建和销毁类的对象(对编译器来说,将一些 "幕后" 行为的代码放在此处也很方便。注意,生成的析构函数一般是非虚拟的,除非它所在的类是从一个声明了虚析构函数的基类继承而来。缺省取址运算符只是返回对象的地址。这些函数实际上就如同下面所定义的那样:

      

    inline Empty::Empty() {}
    inline Empty::
    ~Empty() {}
    inline Empty
    * Empty::operator&() { return this; }
    inline
    const Empty * Empty::operator&() const
    {
    return this; }

      至于拷贝构造函数和赋值运算符,官方的规则是:缺省拷贝构造函数(赋值运算符)对类的非静态数据成员进行 "以成员为单位的" 逐一拷贝构造(赋值)。即,如果m 是类C 中类型为T 的非静态数据成员,并且C 没有声明拷贝构造函数(赋值运算符),m 将会通过类型T 的拷贝构造函数(赋值运算符)被拷贝构造(赋值)---- 如果T 有拷贝构造函数(赋值运算符)的话。如果没有,规则递归应用到m 的数据成员,直至找到一个拷贝构造函数(赋值运算符)或固定类型(例如,int,double,指针,等)为止。默认情况下,固定类型的对象拷贝构造(赋值)时是从源对象到目标对象的 "逐位" 拷贝。对于从别的类继承而来的类来说,这条规则适用于继承层次结构中的每一层,所以,用户自定义的构造函数和赋值运算符无论在哪一层被声明,都会被调用。

      看这样一个NamedObject 模板的定义,它的实例是可以将名字和对象联系起来的类:

      

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

      因为 NamedObject 类声明了至少一个构造函数,编译器将不会生成缺省构造函数;但因为没有声明拷贝构造函数和赋值运算符,编译器将生成这些函数(如果需要的话)。

      看下面对拷贝构造函数的调用:

      

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

      编译器生成的拷贝构造函数必须分别用 no1.nameValue 和no1.objectValue来初始化no2.nameValue 和no2.objectValue。nameValue 的类型是string,string有一个拷贝构造函数,所以no2.nameValue 初始化时将调用string 的拷贝构造函数, 参数为no1.nameValue。另一方面,NamedObject<int>::objectValue 的类型是int(因为这个模板实例中,T 是int),int 没有定义拷贝构造函数,所以no2.objectValue是通过从no1.objectValue 拷贝每一个比特(bit)而被初始化的。

      编译器为 NamedObject<int>生成的赋值运算符也以同样的方式工作,但通常,编译器生成的赋值运算符要想如上面所描述的那样工作,与此相关的所有代码必须合法且行为上要合理。如果这两个条件中有一个不成立,编译器将拒绝为你的类生成operator=,你就会在编译时收到一些诊断信息。

      例如,假设 NamedObject 象这样定义,nameValue 是一个string 的引用,objectValue 是一个const T:

      

    template<class T>
    class NamedObject
    {
    public:
    // 这个构造函数不再有一个const 名字参数,因为nameValue
    // 现在是一个非const string 的引用。char*构造函数
    // 也不见了,因为引用要指向的是string
    NamedObject(string& name, const T& value);
    ...
    // 同上,假设没有
    // 声明 operator=
    private:
    string& nameValue; // 现在是一个引用
    const T objectValue; // 现在为const
    };

      现在看看下面将会发生什么:

      

    string newDog("Persephone");
    string oldDog("Satch");
    NamedObject
    <int> p(newDog, 2); // 正在我写本书时,我们的
    // 爱犬 Persephone 即将过
    // 她的第二个生日
    NamedObject<int> s(oldDog, 29); // 家犬Satch 如果还活着,
    // 会有 29 岁了(从我童年时算起)
    p = s; // p 中的数据成员将会发生
    // 些什么呢?

      赋值之前,p.nameValue 指向某个string 对象,s.nameValue 也指向一个string,但并非同一个。赋值会给p.nameValue 带来怎样的影响呢?赋值之后,p.nameValue 应该指向 "被s.nameValue 所指向的string" 吗,即,引用本身应该被修改吗?如果是这样,那太阳从西边出来了,因为C++没有办法让一个引用指向另一个不同的对象。或者,p.nameValue 所指的string对象应该被修改吗? 这样的话,含有 "指向那个string 的指针或引用" 的其它对象也会受影响,也就是说,和赋值没有直接关系的其它对象也会受影响。这是编译器生成的赋值运算符应该做的吗?

      面对这样的难题,C++拒绝编译这段代码。如果想让一个包含引用成员的类支持赋值,你就得自己定义赋值运算符。对于包含const 成员的类(例如上面被修改的类中的objectValue)来说,编译器的处理也相似;因为修改const成员是不合法的,所以编译器在隐式生成赋值函数时也会不知道怎么办。还有,如果派生类的基类将标准赋值运算符声明为private, 编译器也将拒绝为这个派生类生成赋值运算符。因为,编译器为派生类生成的赋值运算符也应该处理基类部分,但这样做的话,就得调用对派生类来说无权访问的基类成员函数,这当然是不可能的。

      

  • 相关阅读:
    通过字符串调用函数
    First,FirstOrDefault和Single,SingleOrDefault 的区别
    asp循环例子
    将il文件和资源文件生成dll工具
    C# 修改资源文件工具ResourceNet4
    北京ip
    实验1、Mininet 源码安装和可视化拓扑工具
    实验2:Mininet 实验——拓扑的命令脚本生成
    ajax post data 获取不到数据,注意 contenttype的设置 、post/get
    调试提示:当前不会命中断点
  • 原文地址:https://www.cnblogs.com/DanielZheng/p/2132045.html
Copyright © 2020-2023  润新知