为什么这个自定义分配器的析构函数在 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)

不算拷贝构造函数。因此调用了编译器生成的复制构造函数,而您没有观察到。