What's the difference between supplying an STL container (for example, std::vector) with an allocator as a template parameter, eg.:
std::vector<int, std::allocator<int>> some_ints;
and supplying an allocator as a constructor argument, eg:
std::allocator<int> temp;
std::vector<int> some_ints(temp);
and what are the advantages of either, given that they are not the same thing (ie. one supplies a type, the other a type instance) and can be used separately from each other?
Allocators handle all the requests for allocation and deallocation of memory for a given container. The C++ Standard Library provides general-purpose allocators that are used by default, however, custom allocators may also be supplied by the programmer.
A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.
An SGI STL allocator consists of a class with 3 required member functions, all of which are static: void * allocate(size_t n) Allocates an object with the indicated size (in bytes). The object must be sufficiently aligned for any object of the requested size.
A template non-type parameter is a template parameter where the type of the parameter is predefined and is substituted for a constexpr value passed in as an argument. A non-type parameter can be any of the following types: An integral type. An enumeration type. A pointer or reference to a class object.
The template parameter just supplies the type. You still need an instance. It's not separable.
It's like having a function template<typename Type> f(Type instance);
and asking what is the difference between Type
and instance
, can they be used separately and what are the advantages of either. It does not make much sense if you do understand what is a template, type and an instance/object.
(for the sake of simplicity it's c++11)
Here you have type template for vector
:
template<
class T,
class Allocator = std::allocator<T>
> class vector;
And here is the default constructor:
explicit vector( const Allocator& alloc = Allocator() );
There always is an instance of Allocator
provided as alloc
parameter. All other invocation are similar in this regard. By default it is default constructed new Allocator
object. So, semantically, whenever you do not use invocation of vector specifying allocator
parameter, you do create new Allocator
object (which in default case most probably does nothing, but the logical flow of the program is as described).
You cannot pass something that would not fit Allocator
because you would get type-mismatch, or precisely in this case a substitution failure.
One pretty non-standard you could do without touching the definition of vector
is to define DerivedAllocator
which derives from Allocator
instantiate it and pass as an argument. So for example:
vector<T> v( DerivedAllocator<T>() );
But I am not able to come up with a use-case for such construction on the top of my head. There is a good use-case, see the addendum below.
Allocator
template parameter useful for?In some system you have more than one type of memory, so it might be useful to provide separate allocators (presicely separate allocator types). E.g: SRamAllocator
, RamAllocator
, etc.
This is quite common in embedded systems. I know that somewhere there there is a memory model in implementation which actually does not free, when you free it it's a lost chunk. It's essentially a moving pointer. The rationale is that it's extremely fast because it does not have any logic to trace blocks of "holes" caused by free
ing. You wouldn't want to use it scenarios with heavy new
/delete
patterns.
allocator
constructor parameter useful for?It makes sense in case of stateful allocators. Imagine you want to have two storages of the same type. E.g. to track some memory usage, or whatever reason you come with to have more than one logical "memory banks". You may want to create an allocator for each thread in your program, so it's easier to maintain correct CPU/memory affinity.
When you create a new object, you need to tell which of the allocators instances should take care of it.
You could technically implement everything just using different type for each instance, but that would strip down the usability of possible run-time dynamism.
NOTE: Default allocator and pre-c++11 custom allocators are disallowed to have a state, so they basically that to be implemented in a fully static way. It actually does not matter instance of Allocator you use. That is why the default Allocator()
works.
So, theoretically one would no need then to instantiate them, and could work with just type and a static interface... if the standard said so. But it was deliberately not made this way to allow allocator types with an internal state (this sentence is a personal opinion).
IMPORTANT ADDENDUM: I've missed one important perk of c'tor parameter allocator, which is quite possibly it's raison d'être. Polymorphic allocators. Is described in detail here: polymorphic_allocator: when and why should I use it?
Basically, using different Allocator
type would change the whole type of the object, so one end's up with basically the same object which differ only by allocator. This is under certain circumstances highly undesirable. To avoid it, one can write a polymorphic allocators and use base allocator in the type, and concrete implementations as the runtime parameters. Therefore, one can have object of exactly the same type using different storage engines. So using parameter has some overhead, but it reduces status of the allocator from being iron branded onto the type, to more of an implementational detail.
They actually are exactly the same thing.
In the first example, the vector's default constructor default-constructs an allocator of the type you specified.
In the second, you provided the allocator yourself; it happens to match the default type for the container's allocator.
Both examples make use of default arguments; one is a default function argument, and the other is a default template argument. But the end result in each case is precisely the same.
Here's a demonstrative example:
// N.B. I've included one non-defaulted argument to match
// the vector example, but you could omit `T1` entirely and
// write e.g. `Foo<> obj4`.
template <typename T1, typename T2 = int>
struct Foo
{
Foo(T2 x = 42) : x(x) {}
private:
T2 x;
};
int main()
{
Foo<char, int> obj1; // like your first example
Foo<char> obj2(42); // like your second example
Foo<char> obj3; // this is more common
// obj1, obj2 and obj3 are not only of identical type,
// but also have identical state! You just got there
// in different ways.
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With