zoukankan      html  css  js  c++  java
  • 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++虚继承的黑科技

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

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

    待续

  • 相关阅读:
    Linux IO接口 监控 (iostat)
    linux 防火墙 命令
    _CommandPtr 添加参数 0xC0000005: Access violation writing location 0xcccccccc 错误
    Visual Studio自动关闭
    Linux vsftpd 安装 配置
    linux 挂载外部存储设备 (mount)
    myeclipse 9.0 激活 for win7 redhat mac 亲测
    英文操作系统 Myeclipse Console 乱码问题
    Linux 基本操作命令
    linux 查看系统相关 命令
  • 原文地址:https://www.cnblogs.com/moondark/p/3928669.html
Copyright © 2011-2022 走看看