Difference between allocator supplied as template

2019-04-12 05:44发布

问题:

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?

回答1:

Can be used separately from each other?

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.

What is the 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 freeing. You wouldn't want to use it scenarios with heavy new/delete patterns.

What is the 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.



回答2:

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.
}