What is empty class and why worth Optimization
Empty class is an empty class, such
class empty {};
Here, empty is obviously an empty class and has no members. However, empty classes are not limited to this form. For classes that only have member functions and do not have non-static data member, they can also be empty classes.
class empty{public: static void f(); void f();};class empty_too : public empty {};
However, it should be noted that if a class or its base class contains virtual functions, the class is not an empty class, because a class containing virtual functions usually has a vptr, therefore, data members are hidden.
The same applies to the case where the parent class is a virtual base class, because a pointer is required to point to a sub-object of the virtual base class.
For an empty class, its size is not 0, because if it is 0, the two objects will have the same address, so that the two objects cannot be distinguished by addresses simply. Generally, the size of an empty class may be 1 or 4, depending on the compiler.
If a class contains sub-objects of an empty class member, this will cause a certain amount of space waste, which can be avoided, some compilers implement optimization called empty base class optimization.
This optimization is mentioned in the standard:
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 ).
The base class sub-objects can be 0, but the limit is that two sub-objects of the same type cannot be allocated to the same address. Therefore, the technique is to achieve certain optimization by inheriting from an empty class.
class empty1 {};class empty2 {};class non_empty1{public: empty1 o1; empty2 o2;};class non_empty2 : public empty1 , public empty2{};
Here, empty1 and 2 are empty classes. through inheritance, the non_empty2 size is still 1. However, the non_empty1 size is 2.
It should be noted that inheritance may affect the interface, because in the generic code, you do not know whether the class you pass in contains virtual functions. If it contains virtual functions, then there may be a virtual function with the same name as our class, so that our function will be virtualized.
To solve this problem, you can not directly inherit from the empty class, but create an intermediate class and let the class inherit from the empty class. This will limit the impact to our intermediate class. And save the object of the intermediate class as a member.
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;};
In STL, a large number of function objects are used, and many function objects are empty. If you store a large number of these function objects, it will also cause a waste (why should we store them? For example, haha ).
In C ++ template metaprogramming, there is an example of a class that implements a simple composite function 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;};
If f or G is an empty class, it will waste space. Depending on the compiler, composed_fg may occupy up to 8 bytes on 32-bit platforms. However, we optimize the null base class. When f or G has an empty base class, we choose different implementations.
Boost. compressed_pair implements an optimized STD. pair. Let's analyze the implementation of boost. compressed_pair.
Compressed_pair selects different implementations based on the T1 and T2 types. There are 6 cases
T1 = t2 |
T1 empty |
T2 empty |
False |
False |
False |
False |
True |
False |
False |
False |
True |
False |
True |
True |
True |
False |
False |
True |
True |
True |
The difference between t1 = T2 is that C ++ cannot have two identical direct base classes.
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_;};
This is because t1 and t2 are not empty. Here, only two Member objects are saved in the object. Let's take a look at the case where one of them is empty.
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_;};
Here T1 is empty, compressed_pair inherits from T1, but T2 is saved as a member. Another change is that in SWAP, only second is operated. Obviously, because T1 sub-object is empty, swap is meaningless.
The other situations are similar, so you can view the boost source code on your own.
Side noteVc has over-optimized empty base class. For two base class sub-objects of the same type, in G ++, two byte objects are generated, in VC, the size is only one byte.
References[1] The "Empty member" C ++ Optimization
[2] Empty base class or Structure assignment operator may have upt data
[3]
Understanding the empty base optimization
[4] C ++ template metaprogramming
[5]
Why is the size of an empty class not zero?