C++:shared_ptr的隐式转换

博客围绕C++11中智能指针shared_ptr的编译问题展开。使用VS2017和GCC 5.4.0编译含shared_ptr代码时均报错,分析发现是无法将右值转换为non - const引用。指出shared_ptr与裸指针不同,需转换构造函数实现隐式转换,最后介绍了转换构造函数的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近遇到这样一个编译问题,代码是这样的:

#include <memory>
#include <iostream>

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

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

void processSpBase(std::shared_ptr<Base> &p)
{
    std::cout << p.use_count() << std::endl;
}

int main()
{
    std::shared_ptr<Derived> p(new Derived);
    std::cout << p.use_count() << std::endl;
    processSpBase(p);

    return 0;
}

使用VS2017编译报错:

main.cpp:24: error: C2664: “void processSpBase(std::shared_ptr<Base> &)”: 无法将参数 1 从“std::shared_ptr<Derived>”转换为“std::shared_ptr<Base> &”

对着这报错思考了一段时间,未果,中途考虑是难道shared_ptr需要显式转换?查到了std::static_pointer_cast这个标准库模板,把processSpBase调用的一行改成:

processSpBase(std::static_pointer_cast<Base>(p));

仍然报错:

main.cpp:24: error: C2664: “void processSpBase(std::shared_ptr<Base> &)”: 无法将参数 1 从“std::shared_ptr<Base>”转换为“std::shared_ptr<Base> &”

光从错误信息上看,更加费解了。
把原来的代码换了GCC 5.4.0编译,同样报错,但是错误原因写的非常清楚:

./main.cpp: In function ‘int main()’:
./main.cpp:24:20: error: invalid initialization of non-const reference of type ‘std::shared_ptr<Base>&’ from an rvalue of type ‘std::shared_ptr<Base>’
     processSpBase(p);
                    ^
In file included from /usr/include/c++/5/memory:82:0,
                 from ./main.cpp:1:
/usr/include/c++/5/bits/shared_ptr.h:221:2: note:   after user-defined conversion: std::shared_ptr<_Tp>::shared_ptr(const std::shared_ptr<_Tp1>&) [with _Tp1 = Derived; <template-parameter-2-2> = void; _Tp = Base]
  shared_ptr(const shared_ptr<_Tp1>& __r) noexcept
  ^
./main.cpp:16:6: note:   initializing argument 1 of ‘void processSpBase(std::shared_ptr<Base>&)’
 void processSpBase(std::shared_ptr<Base> &p)
      ^

无法把一个右值转换为一个non-const引用。这个右值是从哪里来的呢,很明显只能是从p隐式转换得到的,GCC下面的报错信息也提示到了这一点。
智能指针和裸指针不一样,裸指针Derived *天生的不必经过任何人为干涉而隐式转换为Base *。但是shared_ptr就没办法只靠编译器隐式转换为shared_ptr,必须要写转换构造函数(见C++ Primer 5e 7.5.4一节)来提供隐式转换机制。shared_ptr说到底也只是一个普普通通的模板而已,通过传入不同的模板类型参数而实例化得到的类之间是没有继承关系的,只是模板代码提供的转换构造函数让它看起来像有继承关系,但是转换构造函数会生成一个临时对象,如果没注意到这一点就容易编译不过。我们把processSpBase传入的参数加上一个const,就没有编译问题了。运行一下,结果输出为:

1
2

进入processSpBase后引用计数增加了,可见构造了一个新的shared_ptr。
最后说一下这个模板的转换构造函数是如何实现的,这里关键的一点是使用std::is_convertible来判断隐式转换前后的两个模板参数的类型是否可以转换,直接看代码:

template<typename T>
class FooTemplate
{
public:
    FooTemplate() {}

    template<typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value, void>::type>
    FooTemplate(const FooTemplate<U> &rhs)
    {
        std::cout << "Oh! An implicit cast." << std::endl;
    }
};
<think>好的,我现在需要解释用户的问题:“请使用中文回答我或者给我介绍一下: 将std::shared_ptr用赋值=空”。用户之前已经询问过shared_ptr和weak_ptr的基本概念,现在他们可能想了解如何将一个已存在shared_ptr设置为空,即释放其管理的对象并减少引用计数。 首先,我需要确认用户的具体需求。他们可能在使用shared_ptr时,想要显地释放资源,或者在某个条件满足时断开指针的关联。赋值等于空可能涉及到reset()方法或者直接赋值为nullptr。 接下来,我需要回顾C++shared_ptr的操作。std::shared_ptr可以通过调用reset()成员函数来释放所有权,减少引用计数,并将指针置空。另一种方法是直接将其赋值为nullptr,这同样会触发reset操作。 然后,需要考虑同方法的语法和效果。例如,p.reset()和p = nullptr是否完全等效?在C++11及更高版本中,这两种方法应该是等效的,但可能需要确认标准中的定义。同时,使用reset()可能更明确地表达意图,而赋值nullptr可能更简洁。 还需要注意,当shared_ptr被置空时,如果它是最后一个拥有对象的shared_ptr,管理的对象会被销毁,内存被释放。否则,引用计数减少,但对象仍然存在直到所有shared_ptr都被销毁。 用户可能关心的另一个方面是,如何检查shared_ptr是否为空。可以通过if (p)或者if (p.get() != nullptr)来判断。这在资源管理或条件判断中非常有用。 此外,需要提醒用户避免常见的错误,比如在多个地方使用同一个原始指针创建shared_ptr,这会导致双重释放的问题。但这个问题可能和当前问题关联大,过如果有机会的话可以作为注意事项提到。 可能还需要给出示例代码,说明如何正确地将shared_ptr置空,并展示其效果,比如引用计数的变化和对象的销毁时机。 总结一下,回答的结构可能包括: 1. 使用reset()方法 2. 赋值为nullptr 3. 两者的效果和区别 4. 如何检查shared_ptr是否为空 5. 注意事项和示例代码 需要确保用中文清晰解释,结构分明,步骤详细,并举例说明。同时,注意之前用户提供的系统级指令,比如行内数学表达用$...$,独立公用$$...$$,但这个问题可能涉及数学公,所以重点放在代码示例和结构清晰的解释上。 最后,检查是否有遗漏点,比如为什么需要将shared_ptr置空的应用场景,比如在资源再需要时提前释放,或者在重新分配指针之前释放原有资源等。这些实际应用场景的例子能帮助用户更好地理解。</think>以下是关于将 `std::shared_ptr` 赋值为空的分步说明: --- ### 1. **目的与效果** 将 `std::shared_ptr` 赋值为空的目的是**主动释放其管理的对象所有权**,并减少引用计数。若当前 `shared_ptr` 是最后一个持有对象的指针,对象会被销毁,内存被释放。 --- ### 2. **操作方法** #### 方法 1:使用 `reset()` 成员函数 ```cpp std::shared_ptr<int> p = std::make_shared<int>(42); p.reset(); // 释放对象所有权,引用计数减1;若计数归零,对象被销毁 ``` #### 方法 2:直接赋值为 `nullptr` ```cpp std::shared_ptr<int> p = std::make_shared<int>(42); p = nullptr; // 等效于 p.reset() ``` --- ### 3. **底层行为** - **引用计数变化**:赋值空值会触发 `shared_ptr` 的析构逻辑,引用计数 **-1**。 - **对象释放条件**:若引用计数归零,对象会被销毁。 - **指针状态**:`shared_ptr` 内部指针变为 `nullptr`。 #### 示例分析 ```cpp auto p1 = std::make_shared<int>(100); // 引用计数=1 auto p2 = p1; // 引用计数=2 p1.reset(); // 引用计数=1(p2仍持有对象) p2 = nullptr; // 引用计数=0,对象被销毁 ``` --- ### 4. **检查 `shared_ptr` 是否为空** - 使用布尔转换: ```cpp if (p) { // p 非空,对象有效 } else { // p 为空,对象已释放 } ``` - 使用 `get()` 方法: ```cpp if (p.get() != nullptr) { /* 对象有效 */ } ``` --- ### 5. **应用场景** - **提前释放资源**:再需要对象时主动释放内存。 - **重置指针状态**:准备让 `shared_ptr` 管理新对象前清空旧值。 - **避免循环引用**:结合 `weak_ptr` 主动释放所有权(需配合使用)。 --- ### 6. **注意事项** - ⚠️ **影响其他 `shared_ptr`** 赋值空值仅影响当前指针的引用计数,其他共享同一对象的指针仍有效。 ```cpp auto p1 = std::make_shared<int>(10); auto p2 = p1; p1.reset(); // p2仍持有对象,引用计数=1 ``` - ✅ **优先使用 `reset()` 或 `= nullptr`** 避免直接操作原始指针(如 `p = std::shared_ptr<int>(nullptr)`),保持代码简洁。 - ⚠️ **要重复释放** 若通过 `get()` 获取原始指针并手动 `delete`,会导致未定义行为。 --- ### 7. **完整示例** ```cpp #include <memory> #include <iostream> struct Resource { Resource() { std::cout << "Resource created\n"; } ~Resource() { std::cout << "Resource destroyed\n"; } }; int main() { auto ptr = std::make_shared<Resource>(); // 引用计数=1 auto ptr2 = ptr; // 引用计数=2 ptr.reset(); // 引用计数=1(ptr2仍持有对象) std::cout << "ptr is empty: " << (ptr ? "no" : "yes") << "\n"; ptr2 = nullptr; // 引用计数=0,对象销毁 std::cout << "ptr2 is empty: " << (ptr2 ? "no" : "yes") << "\n"; } ``` #### 输出结果 ```plaintext Resource created ptr is empty: yes Resource destroyed ptr2 is empty: yes ``` --- ### 总结 - **赋值空值**:通过 `reset()` 或 `= nullptr` 显释放 `shared_ptr` 的所有权。 - **引用计数管理**:仅当引用计数归零时对象才会被销毁。 - **安全实践**:避免混合使用原始指针智能指针,优先使用标准方法操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值