为啥这个自定义分配器的析构函数在 GCC/MSVS 的 stdlib 中被调用两次

Posted

技术标签:

【中文标题】为啥这个自定义分配器的析构函数在 GCC/MSVS 的 stdlib 中被调用两次【英文标题】:Why is the destructor of this custom allocator being called twice in GCC/MSVS's stdlib为什么这个自定义分配器的析构函数在 GCC/MSVS 的 stdlib 中被调用两次 【发布时间】:2017-08-12 19:21:23 【问题描述】:

我一直在尝试编写一个简单的分配器,我已经编写了一个只记录其调用的最小分配器。

当尝试在一些简单的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>> v3;
    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)

叮当日志:

    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)

【问题讨论】:

make_unsigned_t 。 xcode 抱怨它 尝试包含&lt;type_traits&gt;,如果你没有设置那个标志,那是C++14类型 能否将错误代码提取成20行左右的代码? 【参考方案1】:

Apparently

template<typename T_rhs>
DebugAllocator(const DebugAllocator<T_rhs>& rhs)

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

【讨论】:

以上是关于为啥这个自定义分配器的析构函数在 GCC/MSVS 的 stdlib 中被调用两次的主要内容,如果未能解决你的问题,请参考以下文章

为啥我在这里的析构函数中删除时创建了一个潜在的流浪指针?

mfc 类的析构函数

为啥自动对象的析构函数被调用两次?

为啥C++里面,析构函数会被调用两次

关于栈的析构问题

C++ 堆栈分配对象,显式析构函数调用