为什么这个自定义分配器的析构函数在 GCC/MSVS 的标准库中被调用两次
Why is the destructor of this custom allocator being called twice in GCC/MSVS's stdlib
我一直在尝试编写一个简单的分配器,我已经编写了一个只记录其调用的最小分配器。
当尝试在一些简单的 std::vector
操作中使用它时 - 至少在 GCC 和 Visual Studio - 记录的行为看起来很直观,除了析构函数似乎在所有分配之前被调用要求,以及最后。在 clang 上,一切都按预期工作,所以我不确定这是否只是一个编译器问题。
假设这不是编译器错误,这个分配器缺少什么;还是我对分配器被调用方式的理解是错误的,这很好?
我有下面的代码作为现场演示 here
#include <ios>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using std::size_t;
struct Indexer
{
static size_t nextId, objectsAlive;
};
size_t Indexer::nextId, Indexer::objectsAlive;
template<typename T>
class DebugAllocator : protected Indexer
{
static std::string formatPointer(const void* p)
{
std::ostringstream s;
s << "[93m0x" << std::hex << std::uppercase << uintptr_t(p) << "[0m";
return s.str();
}
static std::string formatFunctionName(const char* functionName)
{
return "[96m" + std::string(functionName) + "[0m";
}
static std::string indentation()
{
return std::string((objectsAlive + 1) * 4, ' ');
}
public:
using value_type = T;
using pointer = value_type*;
using size_type = std::make_unsigned_t<typename std::pointer_traits<pointer>::difference_type>;
size_t id;
DebugAllocator() noexcept
: id(nextId++)
{
std::cerr << indentation() << "DebugAllocator::" << formatFunctionName(__func__) << '(' << id << ")\n";
++objectsAlive;
}
template<typename T_rhs>
DebugAllocator(const DebugAllocator<T_rhs>& rhs) noexcept
: id(nextId++)
{
std::cerr << indentation() << "DebugAllocator::" << formatFunctionName(__func__) << '(' << id << ", " << rhs.id << ")\n";
++objectsAlive;
}
template<typename T_rhs>
DebugAllocator& operator=(const DebugAllocator<T_rhs>& rhs) noexcept
{
std::cerr << indentation() << id << " = DebugAllocator::" << formatFunctionName(__func__) << '(' << id << ", " << rhs.id << ")\n";
}
~DebugAllocator() noexcept
{
--objectsAlive;
std::cerr << indentation() << "DebugAllocator::" << formatFunctionName(__func__) << '(' << id << ")\n";
}
pointer allocate(size_type n) const
{
value_type* const p((value_type*) new char[sizeof(value_type) * n]);
std::cerr << indentation() << formatPointer(p) << " = DebugAllocator::" << formatFunctionName(__func__) << '(' << id << ", " << n << ")\n";
return p;
}
void deallocate(pointer p, size_type n) const noexcept
{
std::cerr << indentation() << "DebugAllocator::" << formatFunctionName(__func__) << '(' << id << ", " << formatPointer(p) << ", " << n << ")\n";
delete[] (value_type*) p;
}
bool operator==(const DebugAllocator& rhs) const noexcept
{
std::cerr << indentation() << std::boolalpha << true << " = DebugAllocator::" << formatFunctionName(__func__) << '(' << id << ", " << rhs.id << ")\n";
return true;
}
bool operator!=(const DebugAllocator& rhs) const noexcept
{
std::cerr << indentation() << std::boolalpha << false << " = DebugAllocator::" << formatFunctionName(__func__) << '(' << id << ", " << rhs.id << ")\n";
return false;
}
};
int main()
{
std::vector<int, DebugAllocator<int>> v{3};
v.push_back(1);
v.push_back(2);
v.emplace_back(1);
v.insert(std::begin(v) + 2, 4);
v.erase(std::begin(v) + 3);
std::string separator;
for (int& x : v)
{
std::cerr << separator << std::move(x);
separator = ", ";
}
std::cerr << '\n';
}
GCC/MSVS 日志:
DebugAllocator::DebugAllocator(0)
0xF86C50 = DebugAllocator::allocate(0, 1)
DebugAllocator::~DebugAllocator(0)
0xF86CA0 = DebugAllocator::allocate(0, 2)
DebugAllocator::deallocate(0, 0xF86C50, 1)
0xF86C50 = DebugAllocator::allocate(0, 4)
DebugAllocator::deallocate(0, 0xF86CA0, 2)
0xF86C20 = DebugAllocator::allocate(0, 8)
DebugAllocator::deallocate(0, 0xF86C50, 4)
3, 1, 4, 1
DebugAllocator::deallocate(0, 0xF86C20, 8)
DebugAllocator::~DebugAllocator(0)
clang 日志:
DebugAllocator::DebugAllocator(0)
0xD886F0 = DebugAllocator::allocate(0, 1)
0xD88710 = DebugAllocator::allocate(0, 2)
DebugAllocator::deallocate(0, 0xD886F0, 1)
0xD886F0 = DebugAllocator::allocate(0, 4)
DebugAllocator::deallocate(0, 0xD88710, 2)
0xD88730 = DebugAllocator::allocate(0, 8)
DebugAllocator::deallocate(0, 0xD886F0, 4)
3, 1, 4, 1
DebugAllocator::deallocate(0, 0xD88730, 8)
DebugAllocator::~DebugAllocator(0)
template<typename T_rhs>
DebugAllocator(const DebugAllocator<T_rhs>& rhs)
不算拷贝构造函数。因此调用了编译器生成的复制构造函数,而您没有观察到。
我一直在尝试编写一个简单的分配器,我已经编写了一个只记录其调用的最小分配器。
当尝试在一些简单的 std::vector
操作中使用它时 - 至少在 GCC 和 Visual Studio - 记录的行为看起来很直观,除了析构函数似乎在所有分配之前被调用要求,以及最后。在 clang 上,一切都按预期工作,所以我不确定这是否只是一个编译器问题。
假设这不是编译器错误,这个分配器缺少什么;还是我对分配器被调用方式的理解是错误的,这很好?
我有下面的代码作为现场演示 here
#include <ios>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using std::size_t;
struct Indexer
{
static size_t nextId, objectsAlive;
};
size_t Indexer::nextId, Indexer::objectsAlive;
template<typename T>
class DebugAllocator : protected Indexer
{
static std::string formatPointer(const void* p)
{
std::ostringstream s;
s << "[93m0x" << std::hex << std::uppercase << uintptr_t(p) << "[0m";
return s.str();
}
static std::string formatFunctionName(const char* functionName)
{
return "[96m" + std::string(functionName) + "[0m";
}
static std::string indentation()
{
return std::string((objectsAlive + 1) * 4, ' ');
}
public:
using value_type = T;
using pointer = value_type*;
using size_type = std::make_unsigned_t<typename std::pointer_traits<pointer>::difference_type>;
size_t id;
DebugAllocator() noexcept
: id(nextId++)
{
std::cerr << indentation() << "DebugAllocator::" << formatFunctionName(__func__) << '(' << id << ")\n";
++objectsAlive;
}
template<typename T_rhs>
DebugAllocator(const DebugAllocator<T_rhs>& rhs) noexcept
: id(nextId++)
{
std::cerr << indentation() << "DebugAllocator::" << formatFunctionName(__func__) << '(' << id << ", " << rhs.id << ")\n";
++objectsAlive;
}
template<typename T_rhs>
DebugAllocator& operator=(const DebugAllocator<T_rhs>& rhs) noexcept
{
std::cerr << indentation() << id << " = DebugAllocator::" << formatFunctionName(__func__) << '(' << id << ", " << rhs.id << ")\n";
}
~DebugAllocator() noexcept
{
--objectsAlive;
std::cerr << indentation() << "DebugAllocator::" << formatFunctionName(__func__) << '(' << id << ")\n";
}
pointer allocate(size_type n) const
{
value_type* const p((value_type*) new char[sizeof(value_type) * n]);
std::cerr << indentation() << formatPointer(p) << " = DebugAllocator::" << formatFunctionName(__func__) << '(' << id << ", " << n << ")\n";
return p;
}
void deallocate(pointer p, size_type n) const noexcept
{
std::cerr << indentation() << "DebugAllocator::" << formatFunctionName(__func__) << '(' << id << ", " << formatPointer(p) << ", " << n << ")\n";
delete[] (value_type*) p;
}
bool operator==(const DebugAllocator& rhs) const noexcept
{
std::cerr << indentation() << std::boolalpha << true << " = DebugAllocator::" << formatFunctionName(__func__) << '(' << id << ", " << rhs.id << ")\n";
return true;
}
bool operator!=(const DebugAllocator& rhs) const noexcept
{
std::cerr << indentation() << std::boolalpha << false << " = DebugAllocator::" << formatFunctionName(__func__) << '(' << id << ", " << rhs.id << ")\n";
return false;
}
};
int main()
{
std::vector<int, DebugAllocator<int>> v{3};
v.push_back(1);
v.push_back(2);
v.emplace_back(1);
v.insert(std::begin(v) + 2, 4);
v.erase(std::begin(v) + 3);
std::string separator;
for (int& x : v)
{
std::cerr << separator << std::move(x);
separator = ", ";
}
std::cerr << '\n';
}
GCC/MSVS 日志:
DebugAllocator::DebugAllocator(0)
0xF86C50 = DebugAllocator::allocate(0, 1)
DebugAllocator::~DebugAllocator(0)
0xF86CA0 = DebugAllocator::allocate(0, 2)
DebugAllocator::deallocate(0, 0xF86C50, 1)
0xF86C50 = DebugAllocator::allocate(0, 4)
DebugAllocator::deallocate(0, 0xF86CA0, 2)
0xF86C20 = DebugAllocator::allocate(0, 8)
DebugAllocator::deallocate(0, 0xF86C50, 4)
3, 1, 4, 1
DebugAllocator::deallocate(0, 0xF86C20, 8)
DebugAllocator::~DebugAllocator(0)
clang 日志:
DebugAllocator::DebugAllocator(0)
0xD886F0 = DebugAllocator::allocate(0, 1)
0xD88710 = DebugAllocator::allocate(0, 2)
DebugAllocator::deallocate(0, 0xD886F0, 1)
0xD886F0 = DebugAllocator::allocate(0, 4)
DebugAllocator::deallocate(0, 0xD88710, 2)
0xD88730 = DebugAllocator::allocate(0, 8)
DebugAllocator::deallocate(0, 0xD886F0, 4)
3, 1, 4, 1
DebugAllocator::deallocate(0, 0xD88730, 8)
DebugAllocator::~DebugAllocator(0)
template<typename T_rhs>
DebugAllocator(const DebugAllocator<T_rhs>& rhs)
不算拷贝构造函数。因此调用了编译器生成的复制构造函数,而您没有观察到。