zoukankan      html  css  js  c++  java
  • 空基类优化empty base class optimization

    1、为什么C++中不允许类的大小是0

    class ZeroSizeT {};
    ZeroSizeT z[10];
    &z[i] - &z[j];
    
    一般是用两个地址之间的字节数除以类型大小而得到的,而类型大小是0将会出问题

    2、为什么有时多个类组成实例所占空间都是一样的

    复制代码
    class Empty
    { };
    
    class EmptyToo : public Empty
    { };
    
    class EmptyThree : public EmptyToo
    { };
    
    sizeof(Empty) : 1
    sizeof(EmptyToo) : 1
    sizeof(EmptyThree) : 1
    复制代码

    空基类优化:只要不会与同一类型的另一个对象或子对象分配在同一地址,就不需要为其分配空间

    1、为什么C++中不允许类的大小是0

    class ZeroSizeT {};
    ZeroSizeT z[10];
    &z[i] - &z[j];
    
    一般是用两个地址之间的字节数除以类型大小而得到的,而类型大小是0将会出问题

    2、为什么有时多个类组成实例所占空间都是一样的

    复制代码
    class Empty
    { };
    
    class EmptyToo : public Empty
    { };
    
    class EmptyThree : public EmptyToo
    { };
    
    sizeof(Empty) : 1
    sizeof(EmptyToo) : 1
    sizeof(EmptyThree) : 1
    复制代码

    空基类优化:只要不会与同一类型的另一个对象或子对象分配在同一地址,就不需要为其分配空间。

    3、对于空基类优化,如何理解前提条件“只要不会与同一类型的另一个对象或子对象分配在同一地址”,以及why

    复制代码
    class Empty
    { };
    
    class EmptyToo : public Empty
    { };
    
    class EmptyThree : public Empty, public EmptyToo
    { };
    
    sizeof(Empty) : 1
    sizeof(EmptyToo) : 1
    sizeof(EmptyThree) : 2
    复制代码

    NoEmpty的基类Empty和EmptyToo不能分配在同一地址空间,C++内存布局不允许相同类型的子对象偏移量相同。

    对空基类优化进行限制的根本原因在于,我们需要能比较两个指针是否指向同一对象。由于指针几乎总是用地址作内部表示,因此必须保证两个不同的地址对应两个不同的对象。

     更详细的:http://www.programlife.net/the-empty-base-class-optimization.html

    附:

    关于空基类优化的应用:在《C++ Templates The Complete Guide》e_CN的p_435中关于函数对象的组合问题,使用空基类优化的特性,将组合关系改成了继承关系。


    What is empty class and why worth optimization

    Empty class顾名思义就是空类,比如 

    1. class empty {};  

    这里empty显然是一个空类,什么成员都没有。但是空类不限于这种形式,对于只有成员函数,并且没有non-static data member的类,也可以是空类。

    1. class empty  
    2. {  
    3. public:  
    4.     static void f();  
    5.     void f();  
    6. };  
    7.   
    8. class empty_too : public empty {};  
    但是有一点需要注意,如果一个类或者他的基类中包含虚函数,那么该类就不是empty class,因为通常一个含有虚函数的类,都有一个vptr,所以就有了数据成员,虽然是隐藏的。 

    对于父类是虚基类的情况也是一样,因为一般子类需要一个指针指向虚基类的子对象。

    于一个空类,它的大小不是0,因为如果是0,那么两个对象就会拥有相同的地址,这样就无法简单用地址区分两个对象了。通常一个空类的大小可能是1,也可能是4,这取决于编译器。

    如果一个类,它包含空类成员子对象,那么这就会造成一定的空间浪费,而这个浪费是可以避免的,因为有些编译器实现了一种称为empty base class optimization的优化。

    标准中提到了这种优化:

    10/5 Derived classes

    A base class subobject may be of zero size (clause 9); however, two subobjects that have the same class type and that belong to the same most derived object must not be allocated at the same address (5.10).

    基类子对象可以是大小为0的,但是限制是,两个相同类型的子对象不能分配在相同的地址上。所以技巧就是通过从空类来继承是实现一定的优化。

    1. class empty1 {};  
    2. class empty2 {};  
    3.   
    4. class non_empty1  
    5. {  
    6. public:  
    7.     empty1 o1;  
    8.     empty2 o2;  
    9. };  
    10.   
    11. class non_empty2  
    12.     : public empty1  
    13.     , public empty2  
    14. {};  
    这里empty1,2是空类,通过继承,non_empty2的大小还是1。但是non_empty1的大小就是2。

    需要注意的是,继承可能会带来对接口的影响,因为在泛型代码中,你不知道用户传入的类是否包含虚函数,如果包含虚函数,那么可能有一个与我们的类正好同名的虚函数,这样我们的函数就被虚化了。 
    解决这个问题可以不直接从空类继承,而是创建一个中间类,并让这种类来继承空类,这样可以将影响限制在我们的中间类中。并将这个中间类的对象作为成员保存。

    1. class empty {};  
    2. class foo : public empty {}; // not always correct  
    3.   
    4. class foo  
    5. {  
    6.     class bar : public empty {};  
    7.     // ok, the interface of foo is not affected by the inheritance from empty;  
    8. };  
    在stl中,大量用到了函数对象,并且有许多函数对象是空的,如果大量存储这些函数对象也是会造成一定的浪费的(为什么要存储?假设一下,哈哈)。

    在《C++ template metaprogramming》中有这样一个例子:有一个类,实现一个简单的复合函数f(g(x))

    1. template<typename R, typename F, typename G>  
    2. class composed_fg  
    3. {  
    4. public:  
    5.     composed_fg(const F& f, const G& g)  
    6.         : f(f)  
    7.         , g(g)  
    8.     {}  
    9.   
    10.     template<typename T>  
    11.     R operator ()(const T& t) const  
    12.     {  
    13.         return f(g(t));  
    14.     }  
    15. private:  
    16.     F f;  
    17.     G g;  
    18. };  
    这里如果f或者g是空类,那么就会造成空间的浪费,视编译器而定,composed_fg最多可能会在32-bit平台上占用8字节。但是我们进行空基类优化,当f或者g中有空基类时,我们选择不同的实现。 

    boost.compressed_pair就实现了一个优化过的std.pair,我们来分析一下boost.compressed_pair的实现。

    compressed_pair根据T1, T2的类型,来选择不同的实现,有6种情况

    T1 == T2 T1 empty T2 empty
    false false false
    false true false
    false false true
    false true true
    true false false
    true true true

    其中区分T1==T2是因为,C++不允许有2个相同的直接基类。

    What is empty class and why worth optimization
    Empty class顾名思义就是空类,比如 
    [cpp] view plaincopyprint?
    class empty {};  
    这里empty显然是一个空类,什么成员都没有。但是空类不限于这种形式,对于只有成员函数,并且没有non-static data member的类,也可以是空类。
    [cpp] view plaincopyprint?
    class empty  
    {  
    public:  
        static void f();  
        void f();  
    };  
      
    class empty_too : public empty {};  
    但是有一点需要注意,如果一个类或者他的基类中包含虚函数,那么该类就不是empty class,因为通常一个含有虚函数的类,都有一个vptr,所以就有了数据成员,虽然是隐藏的。 
    对于父类是虚基类的情况也是一样,因为一般子类需要一个指针指向虚基类的子对象。
    对于一个空类,它的大小不是0,因为如果是0,那么两个对象就会拥有相同的地址,这样就无法简单用地址区分两个对象了。通常一个空类的大小可能是1,也可能是4,这取决于编译器。
    如果一个类,它包含空类成员子对象,那么这就会造成一定的空间浪费,而这个浪费是可以避免的,因为有些编译器实现了一种称为empty base class optimization的优化。
    标准中提到了这种优化:
    10/5 Derived classes
    A base class subobject may be of zero size (clause 9); however, two subobjects that have the same class type and that belong to the same most derived object must not be allocated at the same address (5.10).
    基类子对象可以是大小为0的,但是限制是,两个相同类型的子对象不能分配在相同的地址上。所以技巧就是通过从空类来继承是实现一定的优化。
    [cpp] view plaincopyprint?
    class empty1 {};  
    class empty2 {};  
      
    class non_empty1  
    {  
    public:  
        empty1 o1;  
        empty2 o2;  
    };  
      
    class non_empty2  
        : public empty1  
        , public empty2  
    {};  
    这里empty1,2是空类,通过继承,non_empty2的大小还是1。但是non_empty1的大小就是2。
    需要注意的是,继承可能会带来对接口的影响,因为在泛型代码中,你不知道用户传入的类是否包含虚函数,如果包含虚函数,那么可能有一个与我们的类正好同名的虚函数,这样我们的函数就被虚化了。 
    解决这个问题可以不直接从空类继承,而是创建一个中间类,并让这种类来继承空类,这样可以将影响限制在我们的中间类中。并将这个中间类的对象作为成员保存。
    [cpp] view plaincopyprint?
    class empty {};  
    class foo : public empty {}; // not always correct  
      
    class foo  
    {  
        class bar : public empty {};  
        // ok, the interface of foo is not affected by the inheritance from empty;  
    };  
    在stl中,大量用到了函数对象,并且有许多函数对象是空的,如果大量存储这些函数对象也是会造成一定的浪费的(为什么要存储?假设一下,哈哈)。
    在《C++ template metaprogramming》中有这样一个例子:有一个类,实现一个简单的复合函数f(g(x))
    [cpp] view plaincopyprint?
    template<typename R, typename F, typename G>  
    class composed_fg  
    {  
    public:  
        composed_fg(const F& f, const G& g)  
            : f(f)  
            , g(g)  
        {}  
      
        template<typename T>  
        R operator ()(const T& t) const  
        {  
            return f(g(t));  
        }  
    private:  
        F f;  
        G g;  
    };  
    这里如果f或者g是空类,那么就会造成空间的浪费,视编译器而定,composed_fg最多可能会在32-bit平台上占用8字节。但是我们进行空基类优化,当f或者g中有空基类时,我们选择不同的实现。 
    boost.compressed_pair就实现了一个优化过的std.pair,我们来分析一下boost.compressed_pair的实现。
    compressed_pair根据T1, T2的类型,来选择不同的实现,有6种情况
    T1 == T2    T1 empty    T2 empty
    false    false    false
    false    true    false
    false    false    true
    false    true    true
    true    false    false
    true    true    true
    其中区分T1==T2是因为,C++不允许有2个相同的直接基类。
    这个是T1和T2都不为空的情况,这里只是简单地在对象中保存了2个成员对象。再来看一下其中一个为空的情况。
    template <class T1, class T2>
    class compressed_pair_imp<T1, T2, 1>
        : protected ::boost::remove_cv<T1>::type
    {
    public:
        typedef T1                                                 first_type;
        typedef T2                                                 second_type;
        typedef typename call_traits<first_type>::param_type       first_param_type;
        typedef typename call_traits<second_type>::param_type      second_param_type;
        typedef typename call_traits<first_type>::reference        first_reference;
        typedef typename call_traits<second_type>::reference       second_reference;
        typedef typename call_traits<first_type>::const_reference  first_const_reference;
        typedef typename call_traits<second_type>::const_reference second_const_reference;
    
        compressed_pair_imp() {}
    
        compressed_pair_imp(first_param_type x, second_param_type y)
            : first_type(x), second_(y) {}
    
        compressed_pair_imp(first_param_type x)
            : first_type(x) {}
    
        compressed_pair_imp(second_param_type y)
            : second_(y) {}
    
        void swap(::boost::compressed_pair<T1,T2>& y)
        {
            // no need to swap empty base class:
            cp_swap(second_, y.second());
        }
    private:
        second_type second_;
    };

    这里T1为空,compressed_pair从T1继承了,然而T2还是作为成员保存起来了。还有一点变化就是swap中,只对second进行了操作,很显然,因为T1子对象是空的,swap没有意义。 
    其他的情况类似了,所以可以自己去看boost的源码。

    Side Note

    VC中存在对Empty base class过度优化的情况,对于2个相同类型基类子对象的情况,在g++中,会生成2个字节大小的对象,而VC中只是1个字节的大小。

    References

    [1] The "Empty Member" C++ Optimization 
    [2] Empty Base Class or Structure Assignment Operator May Corrupt Data 
    [3] Understanding the Empty Base Optimization 
    [4] 《C++ template metaprogramming》 
    [5] Why is the size of an empty class not zero?

    参考:http://blog.csdn.net/seizef/article/details/6168721

    http://www.bubuko.com/infodetail-479676.html

    http://www.cppblog.com/qinqing1984/archive/2011/07/10/150584.aspx

  • 相关阅读:
    【百度地图API】让用户选择起点和终点的驾车导航
    JS解决通过按钮切换图片的问题
    JavaScript (JS)基础:DOM 浅析 (含数组Array、字符串String基本方法解析)
    JavaScript (JS)基础:ECMAScript 浅析 (含Math基本方法解析)
    感谢Sylvia的技术支持
    0904 存储过程、触发器、事务、视图、生成脚本
    0903 连接查询
    0901 子查询
    0831 模糊查询,排序查询,聚合函数,时间日期函数,数学函数,字符串函数
    0829 数据库的增删改查
  • 原文地址:https://www.cnblogs.com/youxin/p/4323216.html
Copyright © 2011-2022 走看看