You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何缩减类型擦除场景下的二进制体积:避免生成专属删除器

轻量shared_ptr删除器的.text段优化方案

问题背景

在固件开发中实现了无弱指针特性的轻量版std::shared_ptr与std::make_shared,控制块与对象绑定在一起销毁。当前make_shared实现会为每种类型生成专属的lambda作为删除器,导致.text段因大量重复代码占用过多空间。希望优化实现,避免为每种类型生成专属lambda,直接利用析构函数逻辑完成销毁。

当前make_shared实现代码:

template <typename T, typename ... ARGS>
shared_ptr<T> make_shared(ARGS&&... args) {
    struct FullBlock {
        _shared_ptr_base::control_block block;
        T value;
    };

    //Allocate block on heap
    auto block = new FullBlock{
        {0, {nullptr, nullptr}},
        T{std::forward<ARGS>(args)...} //value
    };

    block->block.destructor.p = block;
    block->block.destructor.destroy = [](const void* x){
        delete static_cast<const FullBlock*>(x);
    };

    return shared_ptr<T>(&block->block, &block->value);
}

其中block.destructor.p是指向FullBlock(控制块+对象)的类型擦除指针,block.destructor.destroy是执行销毁的lambda函数指针。用户曾设想直接使用类析构函数指针,但该方式不可行:

struct Foo;

using Destructor = void(*)(const void*);

Foo* f;
Destructor d = &Foo::~Foo;
void* ptr = static_cast<void*>(f);

d(ptr); // 未定义行为:析构函数签名不匹配

可行优化方案

方案一:利用虚基类实现通用删除器

通过将FullBlock继承自带虚析构的基类,让所有类型的FullBlock共用同一个删除器函数,彻底避免为每种类型生成销毁逻辑:

// 定义带虚析构的基类
struct FullBlockBase {
    _shared_ptr_base::control_block block;
    virtual ~FullBlockBase() = default;
};

template <typename T, typename ... ARGS>
shared_ptr<T> make_shared(ARGS&&... args) {
    struct FullBlock : FullBlockBase {
        T value;
    };

    auto block = new FullBlock{
        {0, {nullptr, nullptr}},
        T{std::forward<ARGS>(args)...}
    };

    block->block.destructor.p = block;
    // 所有类型共用同一个删除器函数
    block->block.destructor.destroy = [](const void* x){
        delete static_cast<const FullBlockBase*>(x);
    };

    return shared_ptr<T>(&block->block, &block->value);
}

原理:基类的虚析构函数会触发多态调用,自动调用派生类FullBlock<T>的析构函数,进而正确销毁T对象并释放整个FullBlock的内存。此时无论T是什么类型,删除器lambda只会生成一次,大幅减少.text段占用。

方案二:使用模板删除器函数替代lambda

如果不想引入虚函数开销,可以使用模板函数作为删除器,相比lambda,编译器更容易进行代码优化:

template<typename T>
void destroy_full_block(const void* ptr) {
    using FullBlock = struct {
        _shared_ptr_base::control_block block;
        T value;
    };
    delete static_cast<const FullBlock*>(ptr);
}

template <typename T, typename ... ARGS>
shared_ptr<T> make_shared(ARGS&&... args) {
    struct FullBlock {
        _shared_ptr_base::control_block block;
        T value;
    };

    auto block = new FullBlock{
        {0, {nullptr, nullptr}},
        T{std::forward<ARGS>(args)...}
    };

    block->block.destructor.p = block;
    block->block.destructor.destroy = &destroy_full_block<T>;

    return shared_ptr<T>(&block->block, &block->value);
}

该方案仍会为每种T实例化一次destroy_full_block<T>,但模板函数的代码生成通常比lambda更紧凑,且避免了lambda捕获带来的额外隐式逻辑。

为什么直接使用析构函数指针不可行

类的非虚析构函数签名实际为void(T*)(隐式接收this指针),而用户定义的Destructor类型是void(*)(const void*),直接强转函数指针会导致调用时的指针类型不匹配,属于未定义行为,可能引发内存泄漏、程序崩溃等问题。即使是虚析构函数,其底层实现也依赖于虚表,无法直接通过void(*)(const void*)类型的指针正确调用。

内容的提问来源于stack exchange,提问作者Patrick Wright

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 12:10:30