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

如何用智能指针实现C++多态容器?相关实现细节咨询

多态容器的C++智能指针实现方案解析

背景

要实现一个维护多态宠物列表的Vet类,最初的裸指针实现如下:

class Vet{
    std::vector<Pet*> pets;
public:
    void addPet(Pet* pet);  
};

添加宠物的代码:

Vet vet;
vet.addPet(new Dog{});
vet.addPet(new Cat{});

这种方式需要手动在Vet的析构函数中释放动态分配的宠物对象,存在内存泄漏风险。改用智能指针可以自动管理内存,以下针对相关问题逐一分析:


1. std::unique_ptr与std::shared_ptr的选择及适用场景

  • 优先选用std::unique_ptr:如果Vet是宠物对象的唯一所有者(符合诊所管理当前治疗宠物的场景:宠物在治疗期间归属诊所,结束后不再由诊所持有),unique_ptr是最优解。它轻量无额外开销,明确表达“独占所有权”的语义,避免不必要的复杂度。
  • std::shared_ptr的适用场景:仅当需要多个对象共享宠物所有权时使用(比如宠物同时被Vet和主人对象持有)。但此场景下完全不需要,过度使用会引入引用计数开销,甚至可能引发循环引用问题。
  • 无需退回裸指针:只要基类Pet拥有虚析构函数(多态场景的必要条件,与是否使用智能指针无关),智能指针就能自动正确销毁派生类对象,完全替代裸指针的多态功能。

2. std::unique_ptr下addPet的实现方案对比

三个方案各有优劣,需根据调用习惯和语义需求选择:

方案1-1:值传递std::unique_ptr

void addPet(std::unique_ptr<Pet> pet){
    pets.push_back(std::move(pet));
}

调用示例:

vet.addPet(std::make_unique<Dog>());
// 或转移已有指针的所有权
std::unique_ptr<Pet> dog = std::make_unique<Dog>();
vet.addPet(std::move(dog)); // 调用后dog变为空指针

优势:语义清晰,明确要求调用方转移所有权,避免误操作;支持直接传递临时对象(如make_unique的返回值)。
劣势:调用方若想保留原指针需提前拷贝,但此场景下本就该转移所有权,因此不构成问题。
推荐度:★★★★★

方案1-2:左值引用传递std::unique_ptr

void addPet(std::unique_ptr<Pet>& pet){
    pets.push_back(std::move(pet));
}

调用示例:

std::unique_ptr<Pet> dog = std::make_unique<Dog>();
vet.addPet(dog); // 调用后dog变为空指针

优势:调用时无需显式写std::move,语法简洁。
劣势:语义模糊,调用方可能未意识到原指针会被清空;无法直接传递临时对象(如vet.addPet(std::make_unique<Dog>())会编译失败),灵活性差。
推荐度:★★☆☆☆

方案1-3:按类型枚举创建对象

enum class PetType { Dog, Cat };

void addPet(PetType pet){
    if(pet == PetType::Dog) 
        pets.push_back(std::make_unique<Dog>());
    else if(pet == PetType::Cat)
        pets.push_back(std::make_unique<Cat>());
    // 新增宠物类型需修改此处代码
}

调用示例:

vet.addPet(PetType::Dog);
vet.addPet(PetType::Cat);

优势:调用方无需接触指针,封装性强。
劣势:扩展性差,新增宠物类型必须修改addPet代码,违反开闭原则;若宠物需要构造参数,实现会极度繁琐。
推荐度:★★★☆☆(仅适用于固定少量宠物类型的场景)


3. std::shared_ptr下addPet的实现方案对比

若确实需要共享所有权,两个方案的核心区别在于是否转移所有权:

方案2-1:值传递std::shared_ptr

void addPet(std::shared_ptr<Pet> pet){
    pets.push_back(std::move(pet));
}

调用示例:

vet.addPet(std::make_shared<Dog>());
// 或转移已有指针的所有权
std::shared_ptr<Pet> dog = std::make_shared<Dog>();
vet.addPet(std::move(dog)); // dog的引用计数减1

优势:转移所有权时可避免一次引用计数的增减操作,性能略优。
劣势:调用方若误将仍需使用的指针move,会导致空指针问题。
推荐度:★★★☆☆

方案2-2:const左值引用传递std::shared_ptr

void addPet(const std::shared_ptr<Pet>& pet){
    pets.push_back(pet);
}

调用示例:

std::shared_ptr<Pet> dog = std::make_shared<Dog>();
vet.addPet(dog); // 复制指针,引用计数加1
vet.addPet(std::make_shared<Cat>()); // 支持直接传递临时对象

优势:语义安全,调用方明确知道原指针不会被修改;支持临时对象传递,使用灵活。
劣势:每次调用会增加一次引用计数,有微小性能开销,但大多数场景下可忽略。
推荐度:★★★★★


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 13:21:15