![](https://csdnimg.cn/release/download_crawler_static/2634904/bg3.jpg)
Assume the size and natural alignment
of type int are 4 bytes. To ensure
proper alignment of field a, 3 bytes of
padding are inserted after field b in B,
and 3 more bytes of padding are in-
serted after field c in C. (To understand
why, consider arrays whose elements
have type B or C.) Consequently, B has
size 8 while C has size 12.
0
a
84 12
cb
B
However, it is perfectly legitimate to reuse one
of the 3 bytes of padding present at the end of
B to hold the value of field c of C. In this case,
C occupies only 8 bytes, for a nice space saving
of 1/3. This layout trick is known as the tail
padding optimization, and is used by several
C++ compilers, including GCC.
0
a cb
54 8
2.4 Empty base class optimization
Empty classes offer another opportunity for reusing space that is
unused in a base class. Consider:
struct A {};
struct B1 : A {};
struct B2 : A {};
struct C : B1 , B2 { char c1 ; char c2 ; };
C c;
A * a1 = (A *) ( B1 *) & c;
A * a2 = (A *) ( B2 *) & c;
Here, the classes A, B1 and B2 are empty: they
contain zero fields and they do not need any dy-
namic type information. It is tempting to give
them size 0, so that their instances occupy no
memory space at all. However, this would vi-
olate the “object identity” requirement: as de-
picted to the right, the pointers a1 and a2 (to
the two instances of A logically contained within
C) would compare equal, while C++’s semantics
mandate that they are different.
6
c1 c2
a1 == a2
The layout algorithm must therefore insert one
byte of padding in A, resulting in A, B1 and B2
having size 1. Following the naive approach
outlined in section 2.2, C therefore occupies 4
bytes.
c2
B1 B2
a1 a2
c1
However, it is unnecessary to keep the fields
c1 and c2 disjoint from the subobjects of types
B1 and B2: the padding inserted in the latter to
satisfy the object identity requirement can be
reused to hold the values of fields c1 and c2,
as shown to the right.
66
c1 c2
(B2*)c
a2a1
(B1*)c
This technique is known as the empty base class optimization. It is
implemented by many C++ compilers, but often in restricted cases
only. Here is an example where GCC 4.3 misses an opportunity for
reusing the tail padding of an empty base class.
struct A {};
struct B1 : A {};
struct B2 : A {};
struct C : B1 , B2 { char c ; };
struct D : C { char d ; };
As in the previous example, B1 and B2 must be laid out at different
offsets within C, to allow distinguishing the two A contained in C.
Thus, C must have size at least 2. This lower bound can be achieved
by placing c at offset 0, as explained above.
What about D? GCC places d at offset 2, resulting in a size of
3 for D. However, the second byte of C is just padding introduced
to preserve the identity of empty base classes, and it can be reused
to hold data such as field d. This optimized layout fits D in just two
bytes.
Why empty classes matter Over the years, successful C++ soft-
ware, such as the Standard Template Libraries (STL), have become
dependent on C++’s ability to deliver efficient code based on sim-
ple techniques such as empty classes and inlining. Part of the suc-
cess of the STL is based on its archetypical use of function objects:
these are objects of class with the function call operator overloaded.
These objects typically do not carry any runtime data. Rather, their
semantics is their static types. As an example, consider sorting an
array of integers. The STL provides the following template:
template < typename Ran , typename Comp >
void sort(Ran first , Ran last , Comp cmp);
The comparator cmp could be a pointer to function (like the qsort
function in C), but in idiomatic C++ it is any object whose type
overloads the function call operator.
struct MyGreater {
typedef int first_argument_type ;
typedef int second_argument_type ;
typedef bool result_type ;
bool operator ()( int i , int j) const {
return i > j;
}
};
The sort template can, then, be invoked as sort(t, t + n,
MyGreater()). The comparator object constructed by MyGreater()
is of interest not for its runtime data (it carries none), but for its
type: The comparison function is not passed to the sorting routine
through data, but through the type of the object. Consequently, it
is directly called, and in most cases inlined since the body is a
simple integer comparison, which typically reduces to a single
machine instruction. This simple technique, a cornerstone of the
STL success, is effective. One can think of it as a simulation of
dependent types, where data is encoded at the level of types,
therefore making data flow obvious.
The function object technique just described is at the basis of
a composable component of the STL. Composition implies the ex-
istence of a protocol that parts being composed should adhere to.
For example, if we want to combine two unary function objects,
we need a mechanism to ensure that the result type of one func-
tion object agrees with the argument type of the other. To that
end, the STL requires the existence of certain nested types, such
as first_argument_type, second_argument_type and result_type
in the MyGreater class above. To reduce clutter, the STL provides
ready-to-use classes that define those types. In this case, idiomati-
cally, one would write
struct MyGreater :
std:: binary_function <int,in t ,bool > {
bool operator ()( int i , int j) const {
return i > j;
}
};
The sole purpose of std::binary_function<int,int,bool> is to
provide those nested types. It is an empty base class that introduces
no data and no virtual functions. Its semantics is purely static. This
usage, while not object-oriented programming by most popular
definitions, is very common in modern C++ programs.
The pattern is not restricted to only one empty base class: a class
can simulatenously inherit from several empty base classes. This
is the case of bidirectional_input_iterator_tag, which inherits
3 2010/7/15