c++ - 奇怪的 c++ 自定义分配器不匹配的析构函数调用
问题描述
当我跟踪对 STL 容器使用的自定义分配器类的成员函数的调用时,我注意到一些奇怪的行为:对我的分配器类的析构函数的调用与任何早期的构造函数调用都不匹配。这怎么可能?
这是我的代码:
/*
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;
}
这是输出(-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
请注意,对 dtor 进行了两次调用,但对 ctor 进行了一次调用:
- 首先调用 MyAllocator ctor,this=0x7ffe4cee5747
- 然后第二个神秘的 MyAllocator 对象出现在 this=0x7ffe4cee5720,被使用,然后被破坏(没有被构造!?)
- 然后 this=0x7ffe4cee5747 处的第一个 MyAllocator 对象(构造的那个)按预期被破坏。
这里发生了什么?
似乎最简单的解释是我只是忘记了编译器为我生成的某种构造函数。是这样吗?
如果没有,这里有一些其他的想法。
我知道在 c++11 之前,分配器对象必须是“无状态的”。我不确定这到底是什么意思,但也许可以说这意味着,在 c++11 之前,编译器在玩游戏时是合理的,比如制作分配器对象的字节副本,跳过构造函数调用?
但是,无论如何,在 c++11 及更高版本中,分配器应该被允许有状态,对吧?鉴于此,我看不出跳过构造函数调用有何意义。
鉴于这种奇怪的行为,在我看来,我别无选择,只能使用 c++11 之前的限制来实现我的分配器:也就是说,分配器必须是其他资源的无状态句柄。这就是我正在做的,成功的,但我想了解为什么这是必要的。
解决方案
推荐阅读
- java - org.springframework.web.client.ResourceAccessException:“https://[myendpoint]”的 GET 请求出现 I/O 错误
- reactjs - 使用带有 redux 状态的材料表
- javascript - 从 REST API 返回数组中的前 2 个 json 名称/值对
- sql-server - 使用 CTE 和交叉连接的 t-sql 长查询
- r - 在 dplyr 的列中添加噪声
- asp.net-core - Azure AD 身份验证重定向不到本地主机
- d3.js - D3 仅在 select() 运行而不是 selectAll() 时更新每个路径
- c++ - 如何编写一个函数来链接 C 中的其他函数
- css - CSS边框半径如何用于文本
- javascript - Why is firebaseConfig not picked up in javascript?