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

std::shared_ptr如何兼容不完整类型?与std::unique_ptr差异解析

为什么std::shared_ptr能处理不完整类型,而std::unique_ptr不行?

先看一段可正常编译的代码:

#include <memory>

class Somewhere;

struct C {
    std::shared_ptr<Somewhere> here;

    virtual ~C() = default;
};

int main()
{
    C c;
}

但如果把std::shared_ptr替换成std::unique_ptr,编译就会失败——后者要求所持有的类型必须是完整类型。

查阅相关解释后,线索指向动态删除器与静态删除器的区别,我曾假设:std::unique_ptr需要直接删除对象,而std::shared_ptr会在调用std::make_shared(此时所持类型完整的模块)时设置析构逻辑,但查看MSVC版本的std::shared_ptr实现后,没找到具体的处理方式。

核心疑问:当std::shared_ptr的内部引用计数归零时,如果该指针实例所在的位置无法访问所持类型的定义,它是如何调用正确的析构函数销毁对象的?


核心差异:删除器的绑定时机与类型依赖

1. std::unique_ptr的静态删除器限制

std::unique_ptr的默认删除器std::default_delete<T>是模板参数的一部分,它的operator()实现直接包含delete ptr;语句。当unique_ptr销毁对象时,编译器需要生成这段代码,而**delete指向不完整类型的指针属于未定义行为**,因此要求此时T必须是完整类型——编译器需要知道T的析构函数细节,才能正确生成销毁代码。

简单来说,unique_ptr的销毁逻辑是静态绑定的:销毁动作的代码在unique_ptr所在的编译单元生成,必须在该单元可见T的完整定义。

2. std::shared_ptr的动态删除器机制

std::shared_ptr的销毁逻辑是动态绑定的,关键在于它的控制块(control block):

  • 当你创建shared_ptr时(比如通过std::make_shared<T>,或用new T构造shared_ptr),此时T的类型是完整的,编译器会生成一个特定的销毁函数(比如调用delete ptr的函数),并将这个函数的指针存储在控制块中。
  • 控制块同时存储引用计数,当引用计数归零时,shared_ptr会调用控制块中预先存储的销毁函数指针,完全不需要在当前编译单元知道T的完整定义。

举个例子,假设你在某个可见Somewhere完整定义的模块中执行:

std::shared_ptr<Somewhere> p = std::make_shared<Somewhere>();

此时make_shared会生成销毁Somewhere对象的函数,并把函数指针存在控制块里。之后即使把这个shared_ptr传递到一个只知道Somewhere是前向声明的模块,当引用计数归零时,依然能通过控制块里的函数指针调用正确的析构函数。

关于MSVC实现的补充

MSVC的shared_ptr控制块通常包含一个_Deleter成员(或类似的函数指针/可调用对象),这个成员在shared_ptr构造时初始化。比如用new构造shared_ptr时,会默认绑定std::default_delete<T>的实例,此时T完整,default_delete<T>::operator()的实现已经确定;用make_shared时,控制块会直接包含创建和销毁对象的逻辑,同样是在T完整的阶段生成。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 23:23:13