1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
A emi(policy) defines (in some contexts: prescribes) a particular kind of
behavior. In bf(C++) a
hi(class: policy)
em(policy class) defines a certain part of the class interface. It may also
define inner types, member functions, and data members.
In the previous section the problem of creating a class that might use any of
a series of allocation schemes was introduced. These allocation schemes all
depend on the actual data type to use, and so the `template reflex' should
kick in.
Allocation schemes should probably be defined as template classes, applying
the appropriate allocation procedures to the data type at hand. When such
allocation schemes are used by the familiar STL containers (like
tt(std::vector, std::stack), etc.), then such home-made allocation schemes
should probably be derived from tt(std::allocator)hi(allocator), to provide
for the requirements made by these containers. The class template
tt(std::allocator) is declared by the tthi(memory) header file and the three
allocation schemes developed here were all derived from tt(std::allocator).
Using in-class implementations for brevity the following allocation classes
could be defined:
itemization(
it() No special allocation takes place, tt(Data) is used `as is':
verb(template <typename Data>
class PlainAlloc: public std::allocator<Data>
{
template<typename IData>
friend std::ostream &operator<<(std::ostream &out,
PlainAlloc<IData> const &alloc);
Data d_data;
public:
PlainAlloc()
{}
PlainAlloc(Data const &data)
:
d_data(data)
{}
PlainAlloc(PlainAlloc<Data> const &other)
:
d_data(other.d_data)
{}
};)
it() The second allocation scheme uses the standard tt(new) operator to
allocate a new copy of the data:
verb(template <typename Data>
class NewAlloc: public std::allocator<Data>
{
template<typename IData>
friend std::ostream &operator<<(std::ostream &out,
NewAlloc<IData> const &alloc);
Data *d_data;
public:
NewAlloc()
:
d_data(0)
{}
NewAlloc(Data const &data)
:
d_data(new Data(data))
{}
NewAlloc(NewAlloc<Data> const &other)
:
d_data(new Data(*other.d_data))
{}
~NewAlloc()
{
delete d_data;
}
};)
it() The third allocation scheme uses the i(placement new) operator (see
section ref(PLACEMENT)), requesting memory from a
common pool (the implementation of the member tt(request),
obtaining the required amount of memory, is left as an exercise to the
reader):
verb(template<typename Data>
class PlacementAlloc: public std::allocator<Data>
{
template<typename IData>
friend std::ostream &operator<<(std::ostream &out,
PlacementAlloc<IData> const &alloc);
Data *d_data;
static char s_commonPool[];
static char *s_free;
public:
PlacementAlloc()
:
d_data(0)
{}
PlacementAlloc(Data const &data)
:
d_data(new(request()) Data(data))
{}
PlacementAlloc(PlacementAlloc<Data> const &other)
:
d_data(new(request()) Data(*other.d_data))
{}
~PlacementAlloc()
{
d_data->~Data();
}
private:
static char *request();
};)
)
The above three classes define em(policies) that may be selected by the
user of the class tt(Storage) introduced in the previous section. In addition
to these classes, additional allocation schemes could be implemented by the
user as well.
To apply the proper allocation scheme to the class tt(Storage),
tt(Storage) should be designed as a class template itself. The class also
needs a template type parameter allowing users to specify the data type.
The data type to be used by a particular allocation scheme could of course
be specified when specifying the allocation scheme to use. The class
tt(Storage) would then have two template type parameters, one for the data
type, one for the allocation scheme:
verb( template <typename Data, typename Scheme>
class Storage ...)
To use the class tt(Storage) we would then write, e.g.:
verb( Storage<string, NewAlloc<string>> storage;)
Using tt(Storage) this way is fairly complex and potentially error-prone,
as it requires the user to specify the data type twice. Instead, the
allocation scheme should be specified using a new type of template parameter,
not requiring the user to specify the data type required by the allocation
scheme. This new kind of template parameter (in addition to the well-known
em(template type parameter) and em(template non-type parameter)) is called the
emi(template template parameter).
Starting with the C++14 standard the keyword em(class)
hi(template: class vs. typename)
in the syntactical form of template template parameters (tt(template
<parameter specifications> class Name)) is no longer required. From that
standard onward, the keyword tt(typename) can also be used (e.g., tt(template
<parameter specifications> typename Name)).
|