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

如何区分std::shared_ptr通过make_shared创建与绑定已实例化原始指针的状态

如何区分std::shared_ptr的两种非空状态

你提到的问题确实很有意思——常规的if (sp)或者检查sp.get() != nullptr只能判断shared_ptr是否持有有效指针,但没法区分它是通过make_shared直接创建的对象,还是绑定了一个预先用new分配好的原始指针。毕竟这两种情况里,shared_ptr的布尔值都是true,get()也都返回非空地址。

为什么常规检测无效?

先回顾你的测试案例:

  • 案例1(make_shared):shared_ptr会分配一块连续内存,同时存放对象和引用计数控制块,最终持有指向对象的有效指针。
  • 案例3(绑定外部new的指针):shared_ptr会单独分配控制块,对象则存放在之前独立分配的堆内存中,所以get()返回的是外部指针的地址,同样有效。

这两种场景下,shared_ptr的"非空状态"表现完全一致,所以常规方法无法区分。

可行的区分方案

需要说明的是:C++标准并没有提供直接的API来区分这两种情况,因为这属于shared_ptr的内部实现细节。但我们可以通过两种思路来实现检测:

1. 利用控制块与对象的内存连续性(依赖编译器实现)

make_shared的核心优化是将控制块和对象分配在同一块连续内存中,而绑定外部指针时,控制块和对象是分开的两块内存。以GCC/Clang的实现为例,控制块位于对象内存的前方,我们可以通过计算地址来判断:

#include <iostream>
#include <memory>

// 仅适用于GCC/Clang的实现,不保证跨编译器兼容
template<typename T>
bool is_made_via_shared(const std::shared_ptr<T>& sp) {
    if (!sp) return false;
    // 获取控制块地址(GCC中,控制块位于对象内存的前方)
    using ControlBlockType = std::_Sp_counted_ptr_inplace<T, std::allocator<T>, __gnu_cxx::_Lock_policy::_S_atomic>;
    auto* control_block = reinterpret_cast<void*>(reinterpret_cast<char*>(sp.get()) - sizeof(ControlBlockType));
    // 检查shared_ptr的控制块是否指向该地址
    return *reinterpret_cast<void**>(&sp) == control_block;
}

class Entity {};

int main() {
    auto es = std::make_shared<Entity>();
    auto raw_ptr = new Entity;
    auto es3 = std::shared_ptr<Entity>(raw_ptr);

    std::cout << "es is made via make_shared: " << std::boolalpha << is_made_via_shared(es) << std::endl; // 输出true
    std::cout << "es3 is made via make_shared: " << std::boolalpha << is_made_via_shared(es3) << std::endl; // 输出false
    return 0;
}

⚠️ 注意:这种方法依赖特定编译器的shared_ptr实现,换用MSVC等其他编译器可能完全失效,只适合调试或特定环境下使用。

2. 自定义类构造标记(可移植,但需修改类)

如果Entity是你自己维护的类,可以添加一个成员变量来标记对象的创建方式:

#include <iostream>
#include <memory>

class Entity {
private:
    bool created_via_make_shared_ = false;
    // 让make_shared能访问私有构造逻辑
    friend std::shared_ptr<Entity> std::make_shared<Entity>();
public:
    Entity() = default;
    // 用于外部new调用的构造函数,标记为非make_shared创建
    explicit Entity(bool) : created_via_make_shared_(false) {}

    bool is_made_via_shared() const {
        return created_via_make_shared_;
    }
};

// 特化make_shared的逻辑,标记为make_shared创建
template<>
std::shared_ptr<Entity> std::make_shared<Entity>() {
    auto ptr = new Entity;
    ptr->created_via_make_shared_ = true;
    return std::shared_ptr<Entity>(ptr);
}

int main() {
    auto es = std::make_shared<Entity>();
    auto raw_ptr = new Entity(true);
    auto es3 = std::shared_ptr<Entity>(raw_ptr);

    std::cout << "es is made via make_shared: " << es->is_made_via_shared() << std::endl; // true
    std::cout << "es3 is made via make_shared: " << es3->is_made_via_shared() << std::endl; // false
    return 0;
}

这种方法完全符合标准,可移植性强,但需要你能修改目标类的代码,并且要注意避免外部代码绕过标记逻辑。

总结

如果不需要跨编译器,且只是用于调试或特定场景,可以用内存连续性检测的方法;如果需要可移植性,且能修改目标类,自定义构造标记是更稳妥的选择。但要注意,C++标准并不要求shared_ptr必须用连续内存实现make_shared,所以第一种方法始终存在兼容性风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 11:34:04