enable_shared_from_this解析

本文详细解析了C++中的`enable_shared_from_this`模板类的使用场景、实现原理以及常见错误。强调了在对象构造过程中,必须先调用`enable_shared_from_this`构造函数,再调用`shared_ptr`构造函数初始化内部`weak_ptr`,以确保`shared_from_this()`的正确使用。同时提醒程序员避免在构造函数中使用`shared_from_this()`,并坚持统一使用智能指针避免错误。

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

enable_shared_from_this解析

       enable_shared_from_this,是一个以其派生类为模板类型实参的基础模板,继承它,this指针就能变成shared_ptr。

什么时候该使用enable_shared_from_this模板类

       在看下面的例子之前,简单说下使用背景,单有一个类,某个函数需要返回当前对象的指针,我们返回的是shared_ptr,为什么使用智能指针呢,这是因为:当我们使用智能指针管理资源时,必须统一使用智能指针,而不能再某些地方使用智能指针,某些地方使用原始指针,否则不能保持智能指针的语义,从而产生各种错误。好了,介绍完背景,看下面的一段小程序:
      
     程序输出:
     
     从上面的输出你发现了什么,很明显的发现只创建new了一个Test对象,但是却调用了两次析构函数,这对程序来说肯定是一个灾难。为什么会出现这种情况呢?main函数中的boost::shared_ptr<Test> p( new Test( ));将shared_ptr中引用计数器的值设置为1,而在GetObject函数中又通过boost::shared_ptr<Test> pTest(this);又将shared_ptr中的引用计数器的值增加了1,故在析构时一个Test对象被析构了两次。即产生这个错误的原因是通过同一个Test指针对象创建了多个shared_ptr,这是绝对禁止的。同时这也提醒我们在使用shared_ptr时一定不能通过同一个指针对象创建一个以上的shared_ptr对象。那么有什么方法从一个类的成员函数中获取当前对象的shared_ptr呢,其实方法很简单:只需要该类继承至enable_shared_from_this模板类,然后在需要shared_prt的地方调用enable_shared_from_this模板类的成员函数shared_from_this()即可,下面是改进后的代码:
      
      程序输出:
      
      从输出对象只被析构了一次,这是我们想要的结果,因此enable_shared_from_this模板类的作用是:用来作为一个基类,它允许从一个成员函数中获得一个当前对象的shared_ptr。那么enable_shared_from_this模板类到底是如何工作的了?请看下文分解~
      打开enable_shared_from_this.hpp文件,会发现enable_shared_from_this模板类的实现如下:
      
      
     从enable_shared_from_this模板类的实现文件中我们可以很容易的发现我们只能使用返回shared_ptr的shared_from_this()和返回shared_ptr的shared_from_this(),因为这两个版本的shared_from_this()是public权限的,还有一个public权限的是internal_accept_owner函数,但是注释中已经明显指出不能调用这个函数,这个函数会被shared_ptr自动调用,internal_accept_owner函数用来初始化enable_shared_from_this模板类中的唯一成员变量weak_ptr weak_this。而shared_from_this()中是通过将weak_ptr weak_this转化成shared_ptr和shared_ptr来返回的,因此在使用shared_from_this()之前需要先初始化weak_ptr weak_this对象,而weak_ptr weak_this对象是在_internal_accept_owner函数中进行的初始化,也就是说先需要创建shared_ptr对象。即在使用shared_from_this()函数之前,应该先初始化对象的基类enable_shared_from_this,接着再初始化对象,最后初始化shared_ptr。正因为有这个特点所以会出现以下常见的错误:
     先来看情形1:
       

      这种用法明显是错的,虽然对象的基类enable_shared_from_this类的构造函数已经被调用,但是shared_ptr的构造函数并没有被调用,因此weak_ptr weak_this_并没有被初始化,所以这时调用shared_from_this()是错误的。

      接着我们来看情形2:

      

      同样这种做法也是错误的,和情形1同样的原因shared_ptr的构造函数并没有被调用,因此weak_ptr weak_this_并没有被初始化。

正确的做法应该是:

       

shared_ptr<Test> pTest( new Test() );这句话依次执行的顺序是:1 调用enable_shared_from_this的构造函数。2 调用Test的构造函数。 3 调用shared_ptr的构造函数初始化weak_ptr weak_this_。最后才能通过func()函数使用shared_from_this函数。

     从上面的错误中我们知道在使用enable_shared_from_this类中的shared_from_this()函数时应该注意:
1. 不能在对象的构造函数中使用shared_from_this()函数。
2. 先需要调用enable_shared_from_this类的构造函数,接着调用对象的构造函数,最后需要调用shared_ptr类的构造函数初始化enable_shared_from_this的成员变量weak_this_。然后才能使用shared_from_this()函数。 
3. 如何程序中使用了智能指针shared_ptr,则程序中统一使用智能指针,不能使用原始指针,以免出现错误。

     下面给出一个例子:
<span style="background-color: rgb(255, 255, 255);">//
// server.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2008 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#include <ctime>
#include <iostream>
#include <string>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

std::string make_daytime_string()
{
  using namespace std; // For time_t, time and ctime;
  time_t now = time(0);
  return ctime(&now);
}

class tcp_connection
  : public boost::enable_shared_from_this<tcp_connection>
{
public:
  typedef boost::shared_ptr<tcp_connection> pointer;

  static pointer create(boost::asio::io_service& io_service)
  {
    return pointer(new tcp_connection(io_service));
  }

  tcp::socket& socket()
  {
    return socket_;
  }

  void start()
  {
    message_ = make_daytime_string();

    boost::asio::async_write(socket_, boost::asio::buffer(message_),
        boost::bind(&tcp_connection::handle_write, shared_from_this(),
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }

private:
  tcp_connection(boost::asio::io_service& io_service)
    : socket_(io_service)
  {
  }

  void handle_write(const boost::system::error_code& /*error*/,
      size_t /*bytes_transferred*/)
  {
  }

  tcp::socket socket_;
  std::string message_;
};

class tcp_server
{
public:
  tcp_server(boost::asio::io_service& io_service)
    : acceptor_(io_service, tcp::endpoint(tcp::v4(), 13))
  {
    start_accept();
  }

private:
  void start_accept()
  {
    tcp_connection::pointer new_connection =
      tcp_connection::create(acceptor_.io_service());

    acceptor_.async_accept(new_connection->socket(),
        boost::bind(&tcp_server::handle_accept, this, new_connection,
          boost::asio::placeholders::error));
  }

  void handle_accept(tcp_connection::pointer new_connection,
      const boost::system::error_code& error)
  {
    if (!error)
    {
      new_connection->start();
      start_accept();
    }
  }

  tcp::acceptor acceptor_;
};

int main()
{
  try
  {
    boost::asio::io_service io_service;
    tcp_server server(io_service);
    io_service.run();
  }
  catch (std::exception& e)
  {
    std::cerr << e.what() << std::endl;
  }

  return 0;
}</span>

参考资料:
1. http://hahaya.github.io/use-enable-shared-from-this/
2. http://www.boost.org/doc/libs/1_39_0/doc/html/boost_asio/tutorial/tutdaytime3/src.html
<think>我们正在讨论C++中的`shared_from_this`,这是一个用于在类的成员函数中安全地获取指向当前对象的`shared_ptr`的工具。它通常用于当对象已经被`shared_ptr`管理,并且需要传递自身的`shared_ptr`给其他函数或对象的场景。根据引用[2]中的伪代码,我们了解到`enable_shared_from_this`模板类内部有一个`weak_ptr`成员(`self_`),并且在创建`shared_ptr`时,如果被管理的对象是从`enable_shared_from_this`继承的,那么会设置这个`weak_ptr`。这样,我们就可以在对象内部通过`shared_from_this()`成员函数来获得一个共享指针,而不会创建额外的控制块(避免多个独立的`shared_ptr`管理同一个对象,导致多次删除)。使用`shared_from_this`的条件:1.对象必须已经被一个`shared_ptr`所管理(即对象已经被一个`shared_ptr`拥有)。2.类必须公有继承自`enable_shared_from_this<T>`(其中T是类名)。下面是一个使用示例:```cpp#include<iostream>#include<memory>classMyClass:publicstd::enable_shared_from_this<MyClass>{public:voiddo_something(){//我们需要传递一个指向自身的shared_ptr给其他函数std::shared_ptr<MyClass>p=shared_from_this();//使用p...}};intmain(){std::shared_ptr<MyClass>ptr(newMyClass());ptr->do_something();return0;}```注意事项:-不能在构造函数中调用`shared_from_this()`,因为此时对象还没有被`shared_ptr`完全管理(控制块尚未设置好)。-同样,在析构函数中也不应该调用。-如果对象不是由`shared_ptr`管理的(比如在栈上创建),调用`shared_from_this()`会抛出`std::bad_weak_ptr`异常。常见问题:1.在构造函数中调用:这是不允许的,因为`shared_ptr`的构造还没有完成(控制块中的弱引用部分还没有设置)。2.对象不是由`shared_ptr`管理:例如,在栈上创建对象,然后调用成员函数中使用`shared_from_this`,会导致未定义行为(通常抛出异常)。3.多个独立的`shared_ptr`管理同一个对象:如果直接使用裸指针创建多个`shared_ptr`,那么当其中一个`shared_ptr`析构时,对象会被删除,而其他`shared_ptr`成为悬垂指针。使用`shared_from_this`可以避免这种情况,因为它返回的是与已有`shared_ptr`共享所有权的指针。优化:通常推荐使用`std::make_shared`来创建对象,这样可以将控制块和对象本身的内存分配合并,提高效率(引用[1]中提到`std::make_shared`在内存分配上的优势)。下面是一个错误的示例:```cppclassMyClass:publicstd::enable_shared_from_this<MyClass>{public:voidstart(){//错误:如果对象不是由shared_ptr管理,则会导致未定义行为autop=shared_from_this();}};intmain(){MyClassobj;obj.start();//这里会抛出异常,因为obj不在shared_ptr管理之下return0;}```正确的方式是始终使用`shared_ptr`来管理这些对象。另外,在多线程环境中使用`shared_from_this()`需要注意线程安全性,因为对同一个`shared_ptr`的引用计数操作是原子的,所以`shared_from_this()`本身是线程安全的(前提是对象已经被`shared_ptr`管理)。但是,如果多个线程同时调用同一个对象的`shared_from_this()`,返回的多个`shared_ptr`不会相互干扰。总结:`shared_from_this`提供了一种安全获取当前对象共享指针的方法,避免了手动创建`shared_ptr`可能导致的多重管理问题。§§相关问题§§1.在哪些场景下必须使用`shared_from_this`?2.为什么在构造函数中不能调用`shared_from_this()`?3.如何解决`shared_from_this`抛出`std::bad_weak_ptr`异常的问题?4.`enable_shared_from_this`的实现原理是什么?5.在多线程环境下使用`shared_from_this`需要注意什么?</think>### `shared_from_this` 在 C++ 中的用法与常见问题 `std::enable_shared_from_this` 是 C++ 智能指针体系中的关键工具,允许对象安全地获取指向自身的 `std::shared_ptr`,避免创建多个独立的控制块导致资源重复释放。以下是详细解析: #### 核心用法示例 ```cpp #include <memory> #include <iostream> class MyClass : public std::enable_shared_from_this<MyClass> { public: void process() { // 安全获取当前对象的 shared_ptr std::shared_ptr<MyClass> self = shared_from_this(); std::cout << "Use count: " << self.use_count() << std::endl; } }; int main() { auto obj = std::make_shared<MyClass>(); // ✅ 必须由 shared_ptr 管理 obj->process(); // 输出:Use count: 2 } ``` #### 关键规则与原理 1. **继承要求** 类必须**公有继承** `std::enable_shared_from_this<T>`(T 为自身类型),如: ```cpp class MyClass : public std::enable_shared_from_this<MyClass> { ... }; ``` 2. **对象生命周期约束** - 🚫 **禁止在构造函数/析构函数中调用** 此时 `weak_ptr` 成员尚未初始化或已失效(引用[2])。 - ✅ **必须在已有 `shared_ptr` 管理下调用** 对象必须由 `std::shared_ptr` 管理(如通过 `std::make_shared` 创建)。 3. **底层实现机制** - 类内部持有 `std::weak_ptr<T> self_` 成员(引用[2])。 - 当 `std::shared_ptr` 构造时,若对象继承 `enable_shared_from_this`,则自动初始化 `self_`: ```cpp // 伪代码:shared_ptr 构造函数片段 if (对象继承自 enable_shared_from_this<T>) { enable_shared_from_this<T>& base = *ptr; base.self_ = *this; // 绑定 weak_ptr } ``` #### 常见问题与解决方案 1. **抛出 `std::bad_weak_ptr` 异常** **原因**:对象未被 `shared_ptr` 管理时调用 `shared_from_this()`。 **修复**: ```cpp // 错误示例(栈对象) MyClass obj; obj.process(); // ❌ 抛出 bad_weak_ptr // 正确:始终使用 shared_ptr 管理 auto obj = std::make_shared<MyClass>(); ``` 2. **多线程竞争问题** **场景**:多个线程同时调用 `shared_from_this()`。 **方案**:引用计数操作是原子的,但需确保对象不被并发析构: ```cpp void thread_safe_call() { auto self = shared_from_this(); // ✅ 增加引用计数 // 将 self 传递给线程任务 } ``` 3. **与 `std::make_shared` 的协同** 优先使用 `std::make_shared`(引用[1]),它合并控制块与对象内存分配,避免 `shared_from_this` 因内存不足抛出异常。 #### 典型应用场景 1. **异步回调** 在回调中安全持有自身所有权: ```cpp void start_async() { auto self = shared_from_this(); std::thread([self] { self->async_task(); // 保证对象存活 }).detach(); } ``` 2. **观察者模式** 被观察对象将 `shared_from_this()` 传递给观察者。 > 关键点总结:始终通过 `shared_ptr` 管理对象;禁止在构造/析构中调用;优先使用 `std::make_shared`。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值