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

如何简化向基类shared_ptr容器存储派生类对象的save函数实现

问题分析

你当前实现的冗余完全来自不必要的运行时类型检查:

  • 你的容器元素类型本身就是std::shared_ptr<Base>,所有派生类的std::shared_ptr都可以隐式转换为基类智能指针,根本不需要通过std::dynamic_pointer_cast转换后再存储,转换操作对存储逻辑没有任何实际意义。
  • 你把save函数的参数定义为std::shared_ptr<Base>,主动丢弃了传入参数的静态类型信息,才不得不依赖运行时RTTI做类型判断,既增加性能开销,又违反开闭原则,新增派生类必须修改save函数逻辑。
  • 原实现中emplace使用std::piecewise_construct的写法完全多余,直接传入键和值即可。
最优解决方案(零运行时开销,编译期类型检查)

如果调用save时传入的是静态类型明确的派生类智能指针(你给出的示例代码就是这种场景),直接用模板+编译期类型约束实现即可,完全不需要任何运行时类型判断,性能拉满。
核心思路是保留传入参数的静态派生类类型,在编译期完成类型校验和日志匹配,存储时直接隐式转换为基类智能指针。

实现代码(兼容C17,C11可通过模板特化适配)

#include <iostream>
#include <string>
#include <memory>
#include <map>
#include <type_traits>

class Base
{
public:
    Base() {}
    virtual ~Base() {}
};

class Class1 : public Base
{
public:
    Class1() : Base() {}
    ~Class1() {}
};

class Class2 : public Base
{
public:
    Class2() : Base() {}
    ~Class2() {}
};

class Class3 : public Base
{
public:
    Class3() : Base() {}
    ~Class3() {}
};

std::map<std::string, std::shared_ptr<Base>> myContainer;

// 编译期匹配类型对应的日志
template<typename T>
constexpr const char* get_save_log() {
    if constexpr (std::is_same_v<T, Class1>) {
        return "save obj1";
    } else if constexpr (std::is_same_v<T, Class2>) {
        return "save obj2";
    } else if constexpr (std::is_same_v<T, Class3>) {
        return "save obj3";
    } else {
        // 传入不支持的派生类直接编译报错,避免运行时静默失败
        static_assert(!sizeof(T), "Unsupported derived class of Base");
    }
}

// 仅允许Base的派生类传入,编译期完成类型校验
template<typename Derived, typename = std::enable_if_t<std::is_base_of_v<Base, Derived>>>
void save(const std::string& id, std::shared_ptr<Derived> obj)
{
    std::cout << get_save_log<Derived>() << std::endl;
    // 直接存入,派生类智能指针隐式转为基类智能指针,零开销
    myContainer.emplace(id, std::move(obj));
}

int main()
{
    std::shared_ptr<Class1> a1 = std::make_shared<Class1>();
    std::shared_ptr<Class2> a2 = std::make_shared<Class2>();
    std::shared_ptr<Class3> a3 = std::make_shared<Class3>();

    save("id1", a1);
    save("id2", a2);
    save("id3", a3);

    std::cout << "size is " << myContainer.size() << std::endl;
    return 0;
}

方案优势

  • 无任何RTTI、dynamic_cast开销,类型判断全部在编译期完成,性能和原生代码一致
  • 类型安全:传入非Base派生类、未支持的派生类会直接编译报错,不会出现原实现中漏判导致的静默存储失败问题
  • 易扩展:新增派生类时,只需要在get_save_log中新增一个分支即可,不需要修改save核心逻辑
其他可选方案

方案1:多态虚函数(适合必须接收基类指针的场景)

如果你的业务场景中save函数确实只能拿到擦除了静态类型的std::shared_ptr<Base>,可以把类型相关的逻辑下沉到虚函数中,性能远好于逐个dynamic_cast判断。
实现方式是在基类中定义虚接口,每个派生类实现接口返回日志字符串:

class Base
{
public:
    Base() {}
    virtual ~Base() {}
    virtual const char* get_save_log() const = 0;
};

class Class1 : public Base
{
public:
    const char* get_save_log() const override { return "save obj1"; }
    // 其余代码不变
};
// Class2、Class3同理实现get_save_log

void save(const std::string& id, std::shared_ptr<Base> obj)
{
    if (!obj) return;
    std::cout << obj->get_save_log() << std::endl;
    myContainer.emplace(id, std::move(obj));
}

这个方案的运行时开销只有一次虚表寻址,是固定开销,和派生类数量无关,比随派生类数量线性增长的dynamic_cast方案性能高很多。

方案2:std::variant(适合类型集合固定的场景)

如果你所有可能存储的派生类在编译期完全确定,不需要开放扩展,可以用std::variant替代基类多态,完全不需要虚函数和RTTI,性能和模板方案一致。
注意std::tuple不适合该场景,tuple是固定大小的异质集合,无法实现动态键值对的类型擦除存储。
variant实现示例:

// 定义所有允许存储的类型
using Element = std::variant<std::shared_ptr<Class1>, std::shared_ptr<Class2>, std::shared_ptr<Class3>>;
std::map<std::string, Element> myContainer;

template<typename T>
void save(const std::string& id, std::shared_ptr<T> obj)
{
    static_assert(std::is_constructible_v<Element, std::shared_ptr<T>>, "Unsupported type");
    if constexpr (std::is_same_v<T, Class1>) {
        std::cout << "save obj1" << std::endl;
    } else if constexpr (std::is_same_v<T, Class2>) {
        std::cout << "save obj2" << std::endl;
    } else if constexpr (std::is_same_v<T, Class3>) {
        std::cout << "save obj3" << std::endl;
    }
    myContainer.emplace(id, std::move(obj));
}

这个方案的限制是类型集合封闭,新增类型必须修改Element的variant定义,适合类型完全固定的业务场景。

性能对比
方案运行时开销扩展性类型安全
模板编译期方案零好编译期检查
std::variant方案零一般(类型封闭)编译期检查
虚函数多态方案一次虚函数调用(固定开销)好运行时判空
原dynamic_cast遍历方案N次RTTI检查(随派生类数线性增长)差易漏判

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 23:54:32