Empty class and optimization

來源:互聯網
上載者:User

What is empty class and why worth optimization

Empty class顧名思義就是空類,比如 

class empty {};

這裡empty顯然是一個空類,什麼成員都沒有。但是空類不限於這種形式,對於只有成員函數,並且沒有non-static data member的類,也可以是空類。

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的,但是限制是,兩個相同類型的子物件不能分配在相同的地址上。所以技巧就是通過從空類來繼承是實現一定的最佳化。

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。

需要注意的是,繼承可能會帶來對介面的影響,因為在泛型代碼中,你不知道使用者傳入的類是否包含虛函數,如果包含虛函數,那麼可能有一個與我們的類正好同名的虛函數,這樣我們的函數就被虛化了。

解決這個問題可以不直接從空類繼承,而是建立一個中間類,並讓這種類來繼承空類,這樣可以將影響限制在我們的中間類中。並將這個中間類的對象作為成員儲存。

class empty {};class foo : public empty {}; // not always correctclass 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))

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個相同的直接基類。

template <class T1, class T2>class compressed_pair_imp<T1, T2, 0>{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_(x), second_(y) {}    compressed_pair_imp(first_param_type x)        : first_(x) {}    compressed_pair_imp(second_param_type y)        : second_(y) {}    // ...    void swap(::boost::compressed_pair<T1, T2>& y)    {        cp_swap(first_, y.first());        cp_swap(second_, y.second());    }private:    first_type first_;    second_type second_;};

這個是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?

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.