When I trace calls to the member functions of my custom allocator class, as used by an STL container, I notice some strange behavior: there is a call to my allocator class's destructor that isn't matched by any earlier constructor call. How is that possible?
Here's my code:
/*
g++ -pedantic -W -Wall -Werror screwy_allocator_example.cc -o screwy_allocator_example
*/
#include <iostream>
#include <vector>
namespace {
template<class T>
class MyAllocator {
public:
using value_type = T;
template<typename U> struct rebind { using other = MyAllocator<U>; };
MyAllocator() {
std::cout << " in MyAllocator ctor(this="<<this<<")" << std::endl;
std::cout << " out MyAllocator ctor(this="<<this<<")" << std::endl;
}
~MyAllocator() {
std::cout << " in MyAllocator dtor(this="<<this<<")" << std::endl;
std::cout << " out MyAllocator dtor(this="<<this<<")" << std::endl;
}
template<typename From>
MyAllocator(const From &from) {
std::cout << " in MyAllocator copy ctor("
<< "this="<<this<<", &from="<<(void*)&from<<")" << std::endl;
std::cout << " out MyAllocator copy ctor("
<< "this="<<this<<", &from="<<(void*)&from<<")" << std::endl;
}
template<typename From>
MyAllocator &operator=(const From &from) {
std::cout << " in MyAllocator operator=("
<< "this="<<this<<", &from="<<(void*)&from<<")" << std::endl;
std::cout << " out MyAllocator operator=("
<< "this="<<this<<", &from="<<(void*)&from<<")" << std::endl;
return *this;
}
T *allocate(size_t n) {
std::cout << " in MyAllocator::allocate("
<< "this="<<this<<", n="<<n<<")" << std::endl;
// assume n*sizeof(T) doesn't overflow
T *answer = (T*)std::malloc(n * sizeof(T));
// assume answer!=nullptr
std::cout << " out MyAllocator::allocate(this="<<this<<", n="<<n<<")"
<< ", returning "<<(void*)answer << std::endl;
return answer;
}
void deallocate(T *p, size_t n) {
std::cout << " in MyAllocator::deallocate("
<< "this="<<this<<", p="<<(void*)p<<", n="<<n<<")" << std::endl;
std::free(p);
std::cout << " out MyAllocator::deallocate("
<< "this="<<this<<", p="<<(void*)p<<", n="<<n<<")" << std::endl;
}
template<typename U> bool operator==(const MyAllocator<U> &) const
{ return true; }
template<typename U> bool operator!=(const MyAllocator<U> &) const
{ return false; }
}; // class MyAllocator<T>
} // namespace
int main(const int, char**const) {
std::cout << "in main" << std::endl;
{
std::cout << " constructing my_allocator" << std::endl;
MyAllocator<double> my_allocator;
std::cout << " constructed my_allocator" << std::endl;
{
std::cout << " constructing v(my_allocator)" << std::endl;
std::vector<double, MyAllocator<double>> v(my_allocator);
std::cout << " constructed v(my_allocator)" << std::endl;
std::cout << " pushing one item onto v" << std::endl;
v.push_back(3.14);
std::cout << " pushed one item onto v" << std::endl;
std::cout << " destructing v(my_allocator)" << std::endl;
}
std::cout << " destructed v(my_allocator)" << std::endl;
std::cout << " destructing my_allocator" << std::endl;
}
std::cout << " destructed my_allocator" << std::endl;
std::cout << "out main" << std::endl;
return 0;
}
Here's the output (it's the same for -std=c++11, -std=c++14, -std=c++17, -std=c++2a):
in main
constructing my_allocator
in MyAllocator ctor(this=0x7ffe4cee5747)
out MyAllocator ctor(this=0x7ffe4cee5747)
constructed my_allocator
constructing v(my_allocator)
constructed v(my_allocator)
pushing one item onto v
in MyAllocator::allocate(this=0x7ffe4cee5720, n=1)
out MyAllocator::allocate(this=0x7ffe4cee5720, n=1), returning 0x55890a41d2c0
pushed one item onto v
destructing v(my_allocator)
in MyAllocator::deallocate(this=0x7ffe4cee5720, p=0x55890a41d2c0, n=1)
out MyAllocator::deallocate(this=0x7ffe4cee5720, p=0x55890a41d2c0, n=1)
in MyAllocator dtor(this=0x7ffe4cee5720)
out MyAllocator dtor(this=0x7ffe4cee5720)
destructed v(my_allocator)
destructing my_allocator
in MyAllocator dtor(this=0x7ffe4cee5747)
out MyAllocator dtor(this=0x7ffe4cee5747)
destructed my_allocator
out main
Notice that there were two calls to the dtor, but only one call to the ctor:
- first the MyAllocator ctor got called, with this=0x7ffe4cee5747
- then a second mystery MyAllocator object appeared at this=0x7ffe4cee5720, was used, and then was destructed (without ever having been constructed!?)
- then the first MyAllocator object at this=0x7ffe4cee5747 (the one that was constructed) gets destructed as expected.
What's going on here?
It seems the simplest explanation would be that I'm just forgetting about some flavor of constructor that the compiler generates for me. Is that it?
If not, here are some other thoughts.
I know that prior to c++11, allocator objects were required to be "stateless". I'm not sure precisely what that means, but perhaps it could be argued that it implies that, prior to c++11, the compiler could be justified in playing games like making bytewise copies of allocator objects, skipping the constructor calls?
But, regardless, in c++11 and later, allocators are supposedly allowed to have state, right? Given that, I don't see how it can make any sense for the constructor call to be skipped.
Given this strange behavior, it seems to me that I have no choice but to implement my allocator using the pre-c++11 restrictions: that is, the allocator must be nothing more than a stateless handle to some other resource. That's what I'm doing, successfully, but I'd like to understand why this is necessary.