zoukankan      html  css  js  c++  java
  • C++的黑科技(深入探索C++对象模型)

    周二面了腾讯,之前只投了TST内推,貌似就是TST面试了

    其中有一个问题,“如何产生一个不能被继承的类”,这道题我反反复复只想到,将父类的构造函数私有,让子类不能调用,最后归结出一个单例模式,但面试官说,单例模式作为此题的解答不够灵活,后来面试官提示说,可以用友元+虚继承,可以完美实现这样一个类

    当然那时我还不太明白,友元与虚继承我都极少接触过,只是知道有这些东西,回头搜了一下“不能被继承的类”的做法,具体如下:

    1,声明一个类,CNoHeritance,构造函数为private,并声明友元类CParent;
    2,让CParent虚继承CNoHeritance
    这样CParent就成为一个可以被正常实例化,但又不能被继承的类

    吴总当时评价说,“呵呵,虚继承,感觉完全是黑科技啊”

    这个黑科技真是戳中我笑点,但想到C++经常有些奇妙的东西,现在想总结一下

    1,C++构造函数的黑科技

    对于阅读过进阶C++书籍的都该知道,编译器会在“需要”的时候,那么什么是需要的时候呢?四种情况:

    • 1,“带有Default Constructor”的Member Class Object
    • 2,“带有Default Constructor”的Base Class
    • 3,“带有至少一个Virtual Function”的Class
    • 4,“带有一个Virtual Base Class”的Class

    自动合成的构造函数往往都是public,在派生类中,它的构造函数是可以被使用的,即派生类不会因此受到限制。

    那么,如何能使派生类不能使用基类的函数或成员呢?

    • private:只能由:1,该类中的函数;2,其友元函数访问
    • protected:可以被:1,该类中的函数;2,其友元函数;3,派生类(子类)的函数访问
    • public:可以被:1,该类中的函数;2,其友元函数;3,子类的函数;4,该类的对象访问

    如果一个类的构造函数声明为private,则其派生类甚至该类的对象都不能访问,意味着两点:

    • 1,该类不能被继承
    • 2,该类不能由系统实例化,即它实例化的对象不会在栈内存上

    那么怎么使用该类呢?一般而言,会通过该类的函数来创建

    class A
    {
    private:
        A(){}
    public:
        A& createA()
        {
            A* p=new A();
            return *p;
        }
    };

    然而,这样又引申一个问题:类没有实例化,如何能使用其成员函数呢?

    答案是将该成员函数声明为static,这样不需要实例化即可访问,即将上述改为:

    class A
    {
    private:
        A(){}
    public:
        static A& createA()
        {
            A* p=new A();
            return *p;
        }
    };
    
    A Object=A::createA();

    很明显,上面的实例化过程很不方便,简直是艰辛呀,单例模式的其中一种实现就是如此,在此先不讲。这样实现的类,不能被继承,但自己也不好过

    so,如果用友元来实现,是怎么实现的呢?

    声明一个类,及其友元

    class A
    {
    private:
        A(){}
    
        friend class B;
    };

    那么B是可以调用A的private的构造函数的,那么让B虚继承A会发生什么事呢?

    由《深度探索C++对象模型》看到,B内存中将有一份A类的实体,调用A的构造函数构造的,这对于友元类B是可行的

    class A
    {
    private:
        A(){}
    
        friend class B;
    };
    
    class B : virtual A
    {
    };

    那么这样的B能不能被继承呢?假设有个类继承了B,如下

    class A
    {
    private:
        A(){}
    
        friend class B;
    };
    
    class B : virtual A
    {
    };
    
    class C : B
    {
    };

    考虑到虚继承的特性,C也将调用A的构造函数构造出一个A,但!!C并不是A的友元类,所以根本不能执行A私有的构造函数,这段程序,如果不实例化C,编译器不会报错,但一旦实例化C,则将报错。

    而B是可以正常实例化的一个类,这样就完美实现了一个不能被继承的类:B

    2,C++构造函数初始化列表的黑科技

    相比于构造函数的各种trick,C++的初始化列表就显得很容易了,只有那么一点要注意:

    C++的初始化列表的赋值顺序,是与C++类里面成员变量的声明顺序相关,与初始化列表里的顺序无关

    举个例子,以下就会出现莫名错误:

    class A
    {
    public:
        A(int _x, int _y):y(_y), x(y){}
    public:
        int x;
        int y;
    };

    根据声明顺序,在初始化列表中,是先完成x(y)这个步骤,但此时y并没有被赋值,所以得到的x是个随机的值。

    3,C++虚函数的黑科技

    C++虚函数的问题,几乎是面试必问,实际上需要了解的东西也挺多,我自己在前几次面试,都有些理解有误的地方,或者理解不够完善

    这里总结几点吧(以下类都是针对有虚函数的类):

    • 1,每个类都有虚函数表,这个虚函数表是在编译阶段构建,在代码段产生一个vtbl
    • 2,每次实例化的时候,构造函数在前几个字节,产生一个指向虚函数表的指针,指向代码段的那个虚函数表
    • 3,虚函数的实现与调整,是通过移动或变换虚函数表的指针来实现的。
    • 4,纯虚函数是指只声明,但未被实现的虚函数,具有纯虚函数的类不能被实例化,为抽象类

    4,C++拷贝构造函数的黑科技

    C++的拷贝构造函数是C++默认的四个函数之一:构造函数、析构函数、赋值函数、拷贝构造函数

    拷贝构造函数是一种特别的构造函数,在《深度探索C++对象模型》书中说,有三种情况,会导致拷贝构造函数被触发:

    • 1,以一个object的内容作为另一个class object的初始值

      class X {...}
      X x;
      X xx=x;
    • 2,当object被当作参数传递给某个函数时

      void foo(X x);
      X xx;
      foo(xx);
    • 3,函数传回一个class object的时候

      X foo_bar()
      {
        X xx;
        // ...
        return xx;
      }

    一般情况下,如果没有提供explicit copy constructor时,会发生什么呢?

    一个良好的编译器可以为大部分class objects产生bitwise copies,因为它们有bitwise semantics...

    这里说的很神奇,好像我们不需要自己写copy constructor也没问题一样,实际上,bitwise copies在有些情况下是非常不推崇的

    首先解释下什么是bitwise copies:这是指,在拷贝过来的时候,把class的内存直接位拷贝过来,即可以看成是内存拷贝(对应的有值拷贝)

    位拷贝有很多问题,典型的一个,如果class里面含有分配内存的指针,那么它会将指针指向的地址直接拷贝过来:

    class A
    {
    public:
        int *p;
    };
    
    int main()
    {
        A a1;
        a1.p=new int[10];
        A a2=a1;
        cout << a1.p << endl;
        cout << a2.p << endl;
        return 0;
    }

    这里可以发现,a1.p的地址与a2.p的地址是一样的,那么,我分配的内存,该由哪个释放呢?我释放了,另一个怎么办呢?

    实际上,这种拷贝方式在STL的string里面肯定是要重写的,不能用位拷贝。

    《深度探索C++对象模型》中,说class不展现出“bitwise copy semantics”有四种情况:

    • 1,当class含有member object并且后者有一个copy constructor(声明或合成)
    • 2,当class继承一个base class 而后者存在一个copy constructor的时候
    • 3,当class声明了一个或多个virtual functions时
    • 4,当class派生自一个继承串链,其中有一个或多个virtual base classes时

    其实主要都是担心,指针在bitwise semantics下,随便复制可能会导致不可预料的错误

    在这里说一下赋值函数拷贝构造函数在触发上的区别:

    当一个object从无到有时,触发的一定是拷贝构造函数,赋值函数只会在已有的object赋值时,才会触发

    5,C++虚继承的黑科技

    针对虚继承,可以坦承的一点就是

    所有简单的东西,遇到虚继承,似乎都要单独拿出来讨论

  • 相关阅读:
    aud$定位错误用户密码登陆数据库的具体信息
    Linux 磁盘分区、格式化、目录挂载
    Linux RHCS 基础维护命令
    Vertica 6.1不完全恢复启动到LGE方法
    Oracle Recovery 02
    如何删除回滚段状态为NEEDS RECOVERY的undo表空间
    Oracle Recovery 01
    DRA(Data Recovery Advisor)的使用
    Oracle启动报错ORA-27102解决
    jquery remove()不兼容问题解决方案
  • 原文地址:https://www.cnblogs.com/qiaozhoulin/p/5227673.html
Copyright © 2011-2022 走看看