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

能否在无堆分配的情况下实现Getter及其返回类型的类型擦除(自省)?

问题描述

我正尝试为成员Getter编写某种类型擦除自省逻辑,但目前无法避免堆分配。请问是否有办法消除lambda中的new操作?能否借助拷贝消除、引用或移动技巧,绕过中间的类型擦除,将返回值传递到调用者的内存空间中?[调用者内存] <- void* (?) <- GetThing()

示例代码:

#include <functional>
#include <iostream>

struct CopyTest
{
    CopyTest()
    {
        x = 0;
    }

    CopyTest(const CopyTest& rhs)
    {
        x = rhs.x + 1;
    }
    int x = 0;
};

struct Object
{
    CopyTest getValue()
    {
        return val;
    }

    CopyTest val;
};

struct AnotherObject
{
    CopyTest getValue()
    {
        return val;
    }

    CopyTest val;
};

struct TypeErasureBase
{
    uint64_t m_typeInfoPtr;  // 这里我们姑且把它当作typeID示例
    void* m_typeData;
};

template<
    typename ReturnType,
    typename ClassType
>
static std::function<TypeErasureBase(void*)> registerGetMethod(ReturnType(ClassType::* getMethod)(void))
{
    return [=](void* erasedObject) -> TypeErasureBase
    {
        const uint64_t typeID = 1;
        void* typeErasedData = new std::remove_reference_t<ReturnType>((reinterpret_cast<ClassType*>(erasedObject)->*getMethod)());

        return {
            typeID,
            typeErasedData
        };
    };
}

int main(int argc, char* argv[])
{
    const std::function someErasureFunction = registerGetMethod(&Object::getValue);
    const std::function someOtherErasureFunction = registerGetMethod(&AnotherObject::getValue);

    Object valTest;
    AnotherObject anotherTest;

    void* valTestPtr = &valTest;
    void* anotherTestPtr = &anotherTest;

    //...
    /// 在代码库的其他地方,Object和AnotherObject的类型无法直接获取
    //...

    const int val = static_cast<CopyTest*>(someErasureFunction(valTestPtr).m_typeData)->x;
    const int val2 = static_cast<CopyTest*>(someOtherErasureFunction(anotherTestPtr).m_typeData)->x;

    std::cout << "Copies: " << val << std::endl;
    std::cout << "Copies: " << val2 << std::endl;
}

我试过void&或void&&这类奇怪的方式,但现在明白这些没有实际意义。


解决方案

要消除堆分配,核心思路是让类型擦除后的逻辑直接将结果构造到调用者提供的内存空间中,而非在堆上创建对象再返回指针。

1. 调整类型擦除接口

修改类型擦除的结构和注册函数,让调用者传入内存缓冲区地址,由lambda负责将返回值构造到该缓冲区;同时保留类型大小、ID等信息,确保调用者能正确分配内存和销毁对象。

2. 改造后的完整代码

#include <functional>
#include <iostream>
#include <cstddef>

struct CopyTest
{
    CopyTest()
    {
        x = 0;
    }

    CopyTest(const CopyTest& rhs)
    {
        x = rhs.x + 1;
    }
    
    // 可选:添加移动构造进一步减少开销
    CopyTest(CopyTest&& rhs) noexcept
        : x(rhs.x)
    {
        rhs.x = -1;
    }
    
    int x = 0;
};

struct Object
{
    CopyTest getValue()
    {
        return val;
    }

    CopyTest val;
};

struct AnotherObject
{
    CopyTest getValue()
    {
        return val;
    }

    CopyTest val;
};

// 封装类型擦除所需的全部信息:构造逻辑、类型大小、类型ID、析构逻辑
struct TypeErasureInfo
{
    std::function<void(void*, void*)> constructFn; // 参数:被擦除的对象指针、输出缓冲区指针
    size_t typeSize;
    uint64_t typeID;
    std::function<void(void*)> destroyFn; // 析构逻辑,用于清理缓冲区对象
};

template<typename ReturnType, typename ClassType>
static TypeErasureInfo registerGetMethod(ReturnType(ClassType::* getMethod)())
{
    using RawReturnType = std::remove_reference_t<ReturnType>;
    
    return {
        // 直接在调用者缓冲区构造返回值
        [=](void* erasedObject, void* outBuffer) {
            new (outBuffer) RawReturnType(
                (reinterpret_cast<ClassType*>(erasedObject)->*getMethod)()
            );
        },
        sizeof(RawReturnType),
        1, // 示例typeID,实际可使用std::type_index生成唯一标识
        // 封装析构逻辑
        [](void* buffer) {
            static_cast<RawReturnType*>(buffer)->~RawReturnType();
        }
    };
}

int main(int argc, char* argv[])
{
    const auto objErasure = registerGetMethod(&Object::getValue);
    const auto anotherObjErasure = registerGetMethod(&AnotherObject::getValue);

    Object valTest;
    AnotherObject anotherTest;

    void* objPtr = &valTest;
    void* anotherObjPtr = &anotherTest;

    // 调用者用栈内存分配缓冲区(alignas确保内存对齐符合类型要求)
    alignas(CopyTest) char buffer1[sizeof(CopyTest)];
    alignas(CopyTest) char buffer2[sizeof(CopyTest)];

    // 将结果构造到栈缓冲区中
    objErasure.constructFn(objPtr, buffer1);
    anotherObjErasure.constructFn(anotherObjPtr, buffer2);

    // 访问结果
    const int val = static_cast<CopyTest*>(static_cast<void*>(buffer1))->x;
    const int val2 = static_cast<CopyTest*>(static_cast<void*>(buffer2))->x;

    std::cout << "Copies: " << val << std::endl;
    std::cout << "Copies: " << val2 << std::endl;

    // 使用完后调用析构清理
    objErasure.destroyFn(buffer1);
    anotherObjErasure.destroyFn(buffer2);
}

3. 核心优化点

  • 无堆分配:通过placement new直接在调用者提供的栈/堆内存中构造对象,完全避免原代码中的new操作。
  • 减少拷贝:若返回类型支持移动构造,会自动触发移动而非拷贝(可通过CopyTest的移动构造验证)。
  • 内存安全:alignas确保缓冲区内存对齐正确,封装的destroyFn保证对象被正确析构。
  • 灵活适配:调用者可选择栈内存(低开销)或堆内存(大对象场景)作为缓冲区。

4. 扩展方向

  • 若需支持返回引用的Getter,可修改constructFn直接返回引用的指针,无需构造对象。
  • 可将typeID替换为std::type_index,实现更可靠的类型识别。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 18:28:12