Why not to inherit from std::allocator
A lot of people are going to post in this thread that you should not inherit from std::allocator
because it doesn't have a virtual destructor. They'll talk about polymorphism and slicing and deleting via pointer-to-base class, none of which are permitted by the allocator requirements as detailed in section 17.6.3.5 [allocator.requirements] of the standard. Until someone demonstrates that a class derived from std::allocator
fails to meet one of those requirements, it's simple cargo cult mentality.
That said, there is little reason to derive from std::allocator
in C++11. C++11's overhaul of allocators introduced the traits template std::allocator_traits
to sit between an allocator and its users and provide reasonable defaults for many of the required features via template metaprogramming. A minimal allocator in C++11 can be as simple as:
template <typename T>
struct mallocator {
using value_type = T;
mallocator() = default;
template <class U>
mallocator(const mallocator<U>&) {}
T* allocate(std::size_t n) {
std::cout << "allocate(" << n << ") = ";
if (n <= std::numeric_limits<std::size_t>::max() / sizeof(T)) {
if (auto ptr = std::malloc(n * sizeof(T))) {
return static_cast<T*>(ptr);
}
}
throw std::bad_alloc();
}
void deallocate(T* ptr, std::size_t n) {
std::free(ptr);
}
};
template <typename T, typename U>
inline bool operator == (const mallocator<T>&, const mallocator<U>&) {
return true;
}
template <typename T, typename U>
inline bool operator != (const mallocator<T>& a, const mallocator<U>& b) {
return !(a == b);
}
EDIT: Proper use of std::allocator_traits
isn't fully present in all standard libraries yet. For example, the sample allocator above doesn't work correctly with std::list
when compiled with GCC 4.8.1 - the std::list
code complains about missing members since it hasn't been updated yet.
Thou shalt not inherit from std::vector
Actually, there is nothing wrong with public inheritance of std::vector
. If you need this, just do that.
I would suggest doing that only if it is really necessary. Only if you can't do what you want with free functions (e.g. should keep some state).
The problem is that MyVector
is a new entity. It means a new C++ developer should know what the hell it is before using it. What's the difference between std::vector
and MyVector
? Which one is better to use here and there? What if I need to move std::vector
to MyVector
? May I just use swap()
or not?
Do not produce new entities just to make something to look better. These entities (especially, such common) aren't going to live in vacuum. They will live in mixed environment with constantly increased entropy.
Custom allocator method is not called
The inheritance of the standard allocator is not needed. Remove the inheritance : public std::allocator<T>
and the compiler will be kind to inform you about what you missed to implement. Until C++17 the method construct
must be implemented and it is used in std::vector
, not allocate
. Also value_type
, deallocator
and destroy
are missing.
template< class U, class... Args >
void construct( U* p, Args&&... args );
Since you haven't implemented it and your allocator inherits the standard allocator, std::allocator::construct
is called, that does not produce output.
Why you should not inherit std::allocator
The answer on this question is simple on the one hand and not simple in practice on the other hand.
- Like other classes in C++ standard library,
std::allocator
does not have a virtual destructor, so it should not be inherited, if it is not explicitly indicated like instd::enable_shared_from_this
. Standard containers don't use
class Allocator
directly. They usestd::allocator_traits
. It helps to implement minimal user-defined allocators. If you implement onlyvalue_type
,allocate
anddeallocate
members ofMyAllocator
,std::allocator_traits<MyAllocator>
makesMyAllocator
fully conformed to C++ named requirements: Allocator.- Let look at your
MyAllocator
carefully. It inheritsstd::allocator
and "replaces"std::allocator::allocate (2)
. - Let read
std::allocator_traits::allocate (2)
reference manual, that is called bystd::vector
:Calls
a.allocate(n, hint)
if possible. If not possible (e.g. a has no two-argument member function allocate()), callsa.allocate(n)
.
What you have reached. You have implemented
MyAllocator::allocate(std::size_t n)
(2), but notMyAllocator::allocate(std::size_t n, const void* hint)
(1), it is inherited fromstd::allocator
. It is called fromstd::vector
, that is not what you expected. If you had not inheritedstd::allocator
, your implementationMyAllocator::allocate
would be called.- Let look at your
What's the advantage of using std::allocator instead of new in C++?
std::allocator
is the default memory allocator for the standard library containers, and you can substitute your own allocators. This allows you to control how the standard containers allocate memory. But I don't think that your question is about std::allocator
specifically, but rather the strategy of allocating memory, then constucting objects in that memory, rather than using new T[N]
, for example.
And the reason for that is that new T[N]
doesn't allow you control over what constructors are called. And it forces you to construct all your objects at the same time. This is terrible for the purposes of, for example, std::vector
where you only want to allocate occasionally.
With a raw memory allocator, you can allocate a certain amount of memory, which determines your capacity. Then, as the user adds items to the vector (using the constructor of their choice), you can construct objects in place in this memory.
Then when you run out of memory, you allocate more, typically twice as much. If std::vector
used new T[N]
, it would have to reallocate every time you wanted to add or remove an element, which would be terrible for performance. You would also be forced to use the default constructor for all the objects, which puts an unnecessary restriction on the types of objects std::vector
can hold.
Why do C++ allocator requirements not require that construct() constructs an object of value_type?
Note that a.construct(xp, args)
is only optional. A std::vector
merely needs allocate()
to allocate memory and then it can use placement-new
to construct objects in that memory. If the allocator has construct()
then the vector can use it to create objects (also of type T
) in memory previously obtained via allocate()
.
a.allocate
:
Allocates storage suitable for an array object of type
T[n]
and creates the array, but does not construct array elements. May throw exceptions.
And returns a pointer to allocated memory.
I think I don't understand. X can be any type including T. Isn't that mean X can be something that is not T, so vector cannot use construct if the allocator it uses have X which is not equal to T?
No. The list is "Given..." and has the bullet:
xp
, a dereferenceable pointer to some cv-unqualified object typeX
That is, you, the caller, picks some X
. Then the allocator has to meet the requirement that a.construct(xp, args)
constructs an object of type X
. What X
actually is, that is not specified, hence whatever xp
with type X
you choose, the allocator has to meet that (optional) requirement.
As an analogon, consider someone writes some multiplication method and the requirement is:
Given some x and y of type
int
:
- the method returns the result of
x*y
.
Now, "some x and y of type int
" is what you choose, while "the method returns the result of x*y
" is the requirement the method has to fulfill. It's not the method that chooses some x
and some y
and returns 42
always, because there are values x==1
and y==42
where it yields correct results. Rather, it means that for any x
and any y
you choose, the method should fullfill "the method returns the result of x*y
" (for sake of simplicity, I ignored overflow here).
Is it okay to inherit implementation from STL containers, rather than delegate?
The risk is deallocating through a pointer to the base class (delete, delete[], and potentially other deallocation methods). Since these classes (deque, map, string, etc.) don't have virtual dtors, it's impossible to clean them up properly with only a pointer to those classes:
struct BadExample : vector<int> {};
int main() {
vector<int>* p = new BadExample();
delete p; // this is Undefined Behavior
return 0;
}
That said, if you're willing to make sure you never accidentally do this, there's little major drawback to inheriting them—but in some cases that's a big if. Other drawbacks include clashing with implementation specifics and extensions (some of which may not use reserved identifiers) and dealing with bloated interfaces (string in particular). However, inheritance is intended in some cases, as container adapters like stack have a protected member c (the underlying container they adapt), and it's almost only accessible from a derived class instance.
Instead of either inheritance or composition, consider writing free functions which take either an iterator pair or a container reference, and operate on that. Practically all of <algorithm> is an example of this; and make_heap, pop_heap, and push_heap, in particular, are an example of using free functions instead of a domain-specific container.
So, use the container classes for your data types, and still call the free functions for your domain-specific logic. But you can still achieve some modularity using a typedef, which allows you to both simplify declaring them and provides a single point if part of them needs to change:
typedef std::deque<int, MyAllocator> Example;
// ...
Example c (42);
example_algorithm(c);
example_algorithm2(c.begin() + 5, c.end() - 5);
Example::iterator i; // nested types are especially easier
Notice the value_type and allocator can change without affecting later code using the typedef, and even the container can change from a deque to a vector.
Related Topics
Casting via Void* Instead of Using Reinterpret_Cast
C++ Socket Server - Unable to Saturate Cpu
How to Access Private Data Members Outside the Class Without Making "Friend"S
How to Automatically Register a Class on Creation
How to Generate Thread-Safe Uniform Random Numbers
When Is a Function Try Block Useful
Class Template Argument Deduction Not Working with Alias Template
Understanding Double Dispatch C++
Math to Convert Seconds Since 1970 into Date and Vice Versa
How to Extract the Source Filename Without Path and Suffix at Compile Time
Making a Template Parameter a Friend
Why Do We Need to Use 'Int Main' and Not 'Void Main' in C++
A Fast Method to Round a Double to a 32-Bit Int Explained