在《Linux多线程服务器编程:使用muduo C++网络库》这本书中,作者在第15页一针见血的指出C++可能出现的内存问题的几个方面:

  1. 缓冲区溢出(buffer overrun)
  2. 空悬指针(dangling pointer 指向已销毁的对象或已回收的地址)/野指针(wild pointer 指的是未经初始化的指针)
  3. 重复释放(double delete)
  4. 内存泄漏(memory leak)
  5. 不配对的new[]/delete
  6. 内存碎片(memory fragmentation)

正确的使用智能指针可以解决前5个问题:

  1. 缓冲区溢出:使用STL中的容器( std::vector 、 std::string )、使用安全的拷贝函数,或者自己编写buffer class来管理缓冲区,自动记住缓冲区的长度,并通过成员函数,而不是裸指针来修改缓冲区
  2. 空悬指针/野指针:用 std::shared_ptr / std::weak_ptr 
  3. 使用 std::unique_ptr ,只在对象析构时释放一次
  4. 使用 std::unique_ptr ,对象析构的时候自动释放
  5. 不配对的new[]/delete,把new[]统一替换为使用 std::vector 或者使用 std::unique_ptr 管理动态数组,比如std::unique_ptr<int[]> scopedArray(new int[5]);

在前文中,简单讲述了C++中的智能指针,包括 std::unique_ptr 和带引用计数的 std::shared_ptr 。但是单独使用 std::shared_ptr 可能会存在一些问题,所以会有 std::weak_ptr 和 enable_share_from_this 。

weak_ptr


在 C++ 中,智能指针是管理动态分配内存的有效工具,其中 std::shared_ptr 允许多个指针共享对同一个对象的所有权。然而,std::shared_ptr 存在一个问题,即循环引用(循环依赖)。当两个或多个 std::shared_ptr 相互引用形成一个环时,它们的引用计数永远不会降为零,从而导致内存泄漏。std::weak_ptr 就是为了解决这个问题而引入的。

std::weak_ptr 是一种不控制所指向对象生命周期的智能指针,它指向由 std::shared_ptr 管理的对象。std::weak_ptr 与 std::shared_ptr 共享一个控制块(control block),控制块中包含两个计数器:引用计数(reference count)和弱引用计数(weak reference count)。

  • 引用计数:记录有多少个 std::shared_ptr 指向该对象,当引用计数降为零时,对象被销毁。
  • 弱引用计数:记录有多少个 std::weak_ptr 指向该对象,当弱引用计数降为零时,控制块被销毁。

std::weak_ptr 不会增加引用计数,只会增加弱引用计数。当 std::shared_ptr 被销毁时,引用计数减一,当引用计数为零时,对象被销毁,但控制块仍然存在,直到弱引用计数也为零时才会被销毁。这样,std::weak_ptr 可以在不影响对象生命周期的情况下观察对象是否存在。

循环引用


比如以下代码:

#include <iostream>
#include <memory>

class B;

class A
{
public:
    A() { std::cout << "A constructor" << std::endl; }
    ~A() { std::cout << "A destructor" << std::endl; }
    void setB(std::shared_ptr<B> b) { b_ = b; }

private:
    std::shared_ptr<B> b_;
};

class B
{
public:
    B() { std::cout << "B constructor" << std::endl; }
    ~B() { std::cout << "B destructor" << std::endl; }
    void setA(std::shared_ptr<A> a) { a_ = a; }

private:
    std::shared_ptr<A> a_;
};

int main()
{
    {
        std::shared_ptr<A> a = std::make_shared<A>();
        std::shared_ptr<B> b = std::make_shared<B>();
        a->setB(b);
        b->setA(a); // This will create a circular reference
    }
    return 1;
}

上面的代码,如果编译之后运行,可以发现在变量超出作用域之后,并没有调用析构函数:

PS D:\Code\Git\Writing\smartptr> ./main
A constructor
B constructor

这是因为 在上述代码中,A 对象持有一个指向 B 对象的 std::shared_ptrB 对象持有一个指向 A 对象的 std::shared_ptr。当 main 函数结束时,a 和 b 的引用计数都不会降为零,因为它们相互引用,导致 A 和 B 对象的内存无法释放,造成内存泄漏。

解决上述方法也很简单,那就是使用std::weak_ptr,即将上述代码中的 B 对象中的私有成员a_从 std::shared_ptr更换为std::weak_ptr,即可打破循环引用:

class B
{
public:
    B() { std::cout << "B constructor" << std::endl; }
    ~B() { std::cout << "B destructor" << std::endl; }
    void setA(std::shared_ptr<A> a) { a_ = a; }

private:
    std::weak_ptr<A> a_;
};

再次编译运行,可以看到,对象能够正确的析构:

PS D:\Code\Git\Writing\smartptr> ./main.exe
A constructor
B constructor
A destructor
B destructor

将 B 类中的 std::shared_ptr<A> 改为 std::weak_ptr<A> 后,B 对象不再增加 A 对象的引用计数。当 main 函数结束时,a 的引用计数降为零,A 对象被销毁,随后 b 的引用计数也降为零,B 对象被销毁,避免了内存泄漏。

所以在此类循环引用的问题中,通用的做法是:owner持有指向child的std::shared_ptr,child持有指向owner的std::weak_ptr

std::weak_ptr 除了能够解决循环引用之外,因为它还可以监控对象是否存在,所以还可以用在弱回调中。

弱回调


还是在开头的那本书中指出,一个动态创建的对象是否还存活着,光看指针或引用是看不出来的。指针就是指向了一块内存地址,这块内存上的对象如果已经被销毁,那么根本就不能访问,就像free之后的地址不能访问一样,既然不能访问又如何知道对象的状态呢?判断一个指针是不是合法指针没有高效的办法,这是C/C++指针问题的根源(万一原地址又创建了一个新的对象呢?再万一这个新的对象的类型又和老对象不一样呢?)。

在面向对象程序设计中,对象的关系主要有三种:

  • association 关联,是一种最通用的关系,表示一个类的对象与另一个类的对象之间存在某种语义上的联系。关联可以是单向的,也可以是双向的,它只是表明两个类之间存在某种交互,但不强调对象的生命周期关系。比如 person 和 company。
  • aggregation 聚合,是关联的一种特殊形式,它表示整体与部分的关系,部分可以独立于整体而存在。也就是说,整体对象和部分对象的生命周期是相互独立的,整体对象可以包含部分对象,但不负责部分对象的创建和销毁。比如 student 和 classroom。
  • composition 组合,是一种整体与部分的关系,但与聚合不同的是,部分不能独立于整体而存在。也就是说,整体对象负责部分对象的创建和销毁,部分对象的生命周期依赖于整体对象。

最后一种composition 组合关系在多线程里不会遇到麻烦,因为部分的生命周期由唯一的拥有者owner来控制。owner的析构也会把部分对象析构掉。部分是owner的直接数据成员或者是unique_ptr,或者是owner持有的容器元素。

但前两种在C++就比较困难,处理不好就会造成内存泄漏或重复释放。

  • association 关联,从代码形式上看,company持有person的指针或引用,但是person的生命周期不由company单独控制。
  • aggregation 聚合,从形式上看和association相同。只是classroom和student有整体和部分的关系。同样student的生命周期不由company单独控制。

这两种,持有者在调用被持有者的方法时,都必须判断其是否存在,如果被调用者已经为空却还调用其方法,就会导致程序崩溃。判断被持有的对象是否存活,这就是智能指针中 weak_ptr的用作。

下面这个例子还是来自《Linux服务器编程》:

假设有 Stock 类,代表一只股票。每只股票有一个唯一的字符串标识符。 Stock 对象是一个主动对象,它能不断获取新的价格。为了节省系统资源,同一个程序里每只股票只有一个 Stock 对象,如果多处用到同一只股票,那么 Stock 对象应该被共享。如果某一只股票没有再在任何地方用到,其对应的 Stock 对象应该析构,以释放资源,这隐含了”引用计数“。根据上述需求,可以设计一个对象池 StockFactory ,接口很简单,可以根据 key 返回 Stock 对象。在多线程程序中,对象可能被销毁,所以返回 shared_ptr 应该是合理的,代码如下:

class Stock
{
    ......
};

class StockFactory
{
public:
    std::shared_ptr<Stock> get(const std::string &name);
    
private:
    std::mutex mutex_;
    mutable std::map<std::string, std::shared_ptr<Stock>> stocks_;
};

get方法的逻辑很简单,如果在 stocks_ 里找到了 key ,就返回 stocks_[key] ;否则,新建一个 Stock ,并存入 stocks_[key] 。

然而,这段代码的问题在于, get 方法返回的 Stock 对象永远不会被销毁,因为在 map 里面存的是 shared_ptr ,它始终保持着对 Stock 对象的引用。或许应该改为存储一个 weak_ptr ,修改后的代码如下:

class StockFactory
{
public:
    std::shared_ptr<Stock> get(const std::string &name);

private:
    std::mutex mutex_;
    mutable std::map<std::string, std::weak_ptr<Stock>> stocks_;
};

std::shared_ptr<Stock> StockFactory::get(const std::string &name)
{
    std::shared_ptr<Stock> pStock;
    std::lock_guard<std::mutex> lock(mutex_);
    std::weak_ptr<Stock> weakStock = stocks_[name];//如果name不存在,会默认构造一个空的weak_ptr
    pStock = weakStock.lock();
    if (!pStock)
    {
        pStock.reset(new Stock(name));
        weakStock = pStock;//这里更新了stocks_[name]的值,weakStock是一个weak_ptr,所以不会增加引用计数
    }
    return pStock;
}

现在,因为使用的是 weak_ptr ,所以 Stock 对象可以被正常销毁。但这里程序会出现轻微的内存泄漏。这是因为 stocks_ 的大小会只增不减。 stocks_.size() 是曾经存活过的 Stock 对象的总数。

解决办法就是利用 shared_ptr 的定制析构功能。 shared_ptr 的构造函数可以接受额外的一个模板类型参数,传递一个函数指针或者仿函数 d ,在析构对象时执行 d(ptr) ,其中 ptr 是 shared_ptr 保存的对象指针。

class StockFactory
{
public:
    std::shared_ptr<Stock> get(const std::string &name);

private:
    void deleteStock(Stock *stock)
    {
        if (stock)
        {
            std::lock_guard<std::mutex> lock(mutex_);
            stocks_.erase(stock->getName()); // 删除对应的weak_ptr
        }
        delete stock; // 删除stock对象
    }
    std::mutex mutex_;
    mutable std::map<std::string, std::weak_ptr<Stock>> stocks_;
};

std::shared_ptr<Stock> StockFactory::get(const std::string &name)
{
    std::shared_ptr<Stock> pStock;
    std::lock_guard<std::mutex> lock(mutex_);
    std::weak_ptr<Stock> weakStock = stocks_[name]; // 如果name不存在,会默认构造一个空的weak_ptr
    pStock = weakStock.lock();
    if (!pStock)
    {
        pStock.reset(new Stock(name),std::bind(&StockFactory::deleteStock, this, std::placeholders::_1)); // 创建一个新的shared_ptr,并绑定删除器
        weakStock = pStock; // 这里更新了stocks_[name]的值,weakStock是一个weak_ptr,所以不会增加引用计数
    }
    return pStock;
}

这里在 reset 方法中传入了第二个参数,一个 std::bind ,使得它在析构 Stock* p 的时候调用 StockFactory 的 deleteStock 成员函数。

上述代码存在一个问题,那就是把一个原始的 StockFactory this  指针保存在了 std::bind 里,这里会有线程安全问题。如果这个 StockFactory 先于 Stock 对象析构,那么在就会报错。因为需要调用 StockFactory 的 deleteStock 成员函数,但对象本身已经不存在了。这就需要用到弱回调技术。

enable_shared_from_this


在 StockFactory::get 中把原始指针 this 保存到了 std::bind 中,如果 StockFactory 的生命周期比 Stock 短,那么 Stock 析构时去回调 StockFactory::deleteStock 就会报错。现在需要做的就是这里不能直接绑定 this ,而是绑定一个 shared_ptr<StockFactory> ,那么如何获得一个指向当前对象的 shared_ptr<StockFactory> 。

答案就是 enable_shared_from_this ,即让当前的对象派生自 std::enable_shared_from_this<T> 。

class StockFactory:std::enable_shared_from_this<StockFactory>
{
public:
    std::shared_ptr<Stock> get(const std::string &name);

private:
    void deleteStock(Stock *stock)
    {
        if (stock)
        {
            std::lock_guard<std::mutex> lock(mutex_);
            stocks_.erase(stock->getName()); // 删除对应的weak_ptr
        }
        delete stock; // 删除stock对象
    }
    std::mutex mutex_;
    mutable std::map<std::string, std::weak_ptr<Stock>> stocks_;
};

std::shared_ptr<Stock> StockFactory::get(const std::string &name)
{
    std::shared_ptr<Stock> pStock;
    std::lock_guard<std::mutex> lock(mutex_);
    std::weak_ptr<Stock> weakStock = stocks_[name]; // 如果name不存在,会默认构造一个空的weak_ptr
    pStock = weakStock.lock();
    if (!pStock)
    {
        pStock.reset(new Stock(name),std::bind(&StockFactory::deleteStock, shared_from_this(), std::placeholders::_1)); // 创建一个新的shared_ptr,并绑定删除器
        weakStock = pStock; // 这里更新了stocks_[name]的值,weakStock是一个weak_ptr,所以不会增加引用计数
    }
    return pStock;
}

继承自 std::enable_shared_from_this<T> ,然后 shared_from_this 方法返回的对象就是 this 指针的 shared_ptr 。

为了能使用 shared_from_this() , StockFactory 不能是堆上的对象,必须是栈上的对象,并且有 shared_ptr 来管理它的生命周期,即:

std::shared_ptr<StockFactory> factory = std::make_shared<StockFactory>();

现在, std::bind 里面保存了一份 share_ptr<StockFactory> ,可以保证调用 StockFactory::deleteStock 的时候 StockFactory 对象还活着。

需要特别注意的是, shared_from_this() 不能在构造函数里调用,因为在构造 StockFactory 的时候,它还没有被交由 shared_ptr 接管。最后一个问题就是, StockFactory 的生命周期被意外延长了。

现在就是”弱回调“登场了。

把 shared_ptr 绑到 std::bind 中,那么回调的时候 StockFactory 对象始终存在,是安全的。但同时也延长了对象的生命周期,使得它不能短于绑定的 std::bind 对象。有时候我们需要”如果对象还活着,就调用它的成员函数,否则忽略“的语意。这就是”弱回调“,利用 weak_ptr ,将 weak_ptr 绑定到 std::bind 里,这样对象的生命周期就不会被延长。然后在回调的时候,先尝试提升为 shared_ptr ,如果提升成功,说明接受回调的对象还存在,那么就执行回调;如果提升失败,就不作任何处理。根据这一思想,上面的代码可以修改为:

class StockFactory :public std::enable_shared_from_this<StockFactory>
{
public:
   
    std::shared_ptr<Stock> get(const std::string &name)
    {
        std::shared_ptr<Stock> pStock;
        std::lock_guard<std::mutex> lock(mutex_);
        std::weak_ptr<Stock> weakStock = stocks_[name]; // 如果name不存在,会默认构造一个空的weak_ptr
        pStock = weakStock.lock();
        if (!pStock)
        {
            pStock.reset(new Stock(name), std::bind(&StockFactory::weakDeleteCallback, std::weak_ptr<StockFactory>(shared_from_this()), std::placeholders::_1)); // 创建一个新的shared_ptr,并绑定删除器
            weakStock = pStock;                                                                                                                                  // 这里更新了stocks_[name]的值,weakStock是一个weak_ptr,所以不会增加引用计数
        }
        return pStock;
    }

private:
    static void weakDeleteCallback(const std::weak_ptr<StockFactory> &wkFactory, Stock *stock)
    {
        printf("weakDeleteStock[%p]\n", stock);
        std::shared_ptr<StockFactory> factory = wkFactory.lock(); // 获取shared_ptr
        if (factory)
        {
            factory->removeStock(stock); // 删除对应的weak_ptr
        }
        else
        {
            printf("factory died.\n");
        }
        delete stock; // 删除stock对象
    }

    void removeStock(Stock *stock)
    {
        if (stock)
        {
            std::lock_guard<std::mutex> lock(mutex_);
            printf("erased stock [%p]\n", stock);
            stocks_.erase(stock->getName()); // 删除对应的weak_ptr
        }else
        {
            printf("stock is nullptr.\n");
        }
       
    }
    std::mutex mutex_;
    mutable std::map<std::string, std::weak_ptr<Stock>> stocks_;
};

注意,这里一定要 public 继承自  std::enable_shared_from_this<StockFactory> ,否则会报如下错误:

terminate called after throwing an instance of 'std::bad_weak_ptr'
  what():  bad_weak_ptr

 shared_from_this()  被调用时,如果对象还未被  std::shared_ptr  管理,就会抛出异常。通常, shared_from_this()  要在对象已经被  std::shared_ptr  管理之后才能调用。

现在运行两个简单的测试:

void testLonglifeFactory()
{
    std::shared_ptr<StockFactory> factory =  std::make_shared<StockFactory>();
    {
        std::shared_ptr<Stock> stock1 = factory->get("AAPL");
        std::shared_ptr<Stock> stock2 = factory->get("AAPL");
        std::cout << (stock1 == stock2) << std::endl;
    }
}

void testShortlifeFactory()
{
    std::shared_ptr<Stock> stock1;
    {
        std::shared_ptr<StockFactory> factory = std::make_shared<StockFactory>();
        stock1 = factory->get("AAPL");
        std::shared_ptr<Stock> stock2 = factory->get("AAPL");
        std::cout << (stock1 == stock2) << std::endl;
    }
}

int main()
{
    testLonglifeFactory();
    testShortlifeFactory();
    return 0;
};

上面的代码输出结果如下:

0
weakDeleteStock[0000014b80f51ab0]
erased stock[0000014b80f51ab0]
weakDeleteStock[0000014b80f51a40]
erased stock[0000014b80f51a40]

0
weakDeleteStock[0000014b80f51ab0]
erased stock[0000014b80f51ab0]
weakDeleteStock[0000014b80f51a40]
factory died.

在 testLongLife 中, factory 的生命周期会长于两个 stock ,所以当 stock 析构时, factory 能正常移除。在 testShortLife 中, stock1 的生命周期最长,其次是 factory ,最后是 stock2 ,所以可以看到, stock2 可以被正常移除,当试图析构 stock1 时, factory 已经出了作用域被提前析构了。如果在 StockFactory 和 Stock 中添加对析构函数调用的打印日志,结果会更明显:

0
weakDeleteStock[0000021b3a1e1ab0]
erased stock [0000021b3a1e1ab0]
~Stock
weakDeleteStock[0000021b3a1e1a40]
erased stock [0000021b3a1e1a40]
~Stock
~StockFactory


0
weakDeleteStock[0000021b3a1e1ab0]
erased stock [0000021b3a1e1ab0]
~Stock
~StockFactory
weakDeleteStock[0000021b3a1e1a40]
factory died.
~Stock

可以看到,无论 Stock 和 StockFactory 谁先会被析构都不会影响程序的正确运行。这里借助 shared_ptr 和 weak_ptr 完美地解决了两个对象相互引用的问题。

muduo中的应用


在 muduo 网络库中,也使用了 weak_ptr 。一个 TcpConnection 包含了一个 Channel 。

class TcpConnection : noncopyable, public std::enable_shared_from_this<TcpConnection>
{
public:
    TcpConnection(EventLoop *loop, 
                const std::string &name, 
                int sockfd,
                const InetAddress& localAddr,
                const InetAddress& peerAddr);
    ~TcpConnection();

    EventLoop* getLoop() const { return loop_; }
    const std::string& name() const { return name_; }
    const InetAddress& localAddress() const { return localAddr_; }
    const InetAddress& peerAddress() const { return peerAddr_; }

    bool connected() const { return state_ == kConnected; }

    // 发送数据
    void send(const std::string &buf);
    // 关闭连接
    void shutdown();

    void setConnectionCallback(const ConnectionCallback& cb)
    { connectionCallback_ = cb; }

    void setMessageCallback(const MessageCallback& cb)
    { messageCallback_ = cb; }

    void setWriteCompleteCallback(const WriteCompleteCallback& cb)
    { writeCompleteCallback_ = cb; }

    void setHighWaterMarkCallback(const HighWaterMarkCallback& cb, size_t highWaterMark)
    { highWaterMarkCallback_ = cb; highWaterMark_ = highWaterMark; }

    void setCloseCallback(const CloseCallback& cb)
    { closeCallback_ = cb; }

    // 连接建立
    void connectEstablished();
    // 连接销毁
    void connectDestroyed();
private:
    enum StateE {kDisconnected, kConnecting, kConnected, kDisconnecting};
    void setState(StateE state) { state_ = state; }

    void handleRead(Timestamp receiveTime);
    void handleWrite();
    void handleClose();
    void handleError();

    void sendInLoop(const void* message, size_t len);
    void shutdownInLoop();

    EventLoop *loop_; // 这里绝对不是baseLoop, 因为TcpConnection都是在subLoop里面管理的
    const std::string name_;
    std::atomic_int state_;
    bool reading_;

    // 这里和Acceptor类似   Acceptor=》mainLoop    TcpConenction=》subLoop
    std::unique_ptr<Socket> socket_;
    std::unique_ptr<Channel> channel_;

    const InetAddress localAddr_;
    const InetAddress peerAddr_;

    ConnectionCallback connectionCallback_; // 有新连接时的回调
    MessageCallback messageCallback_; // 有读写消息时的回调
    WriteCompleteCallback writeCompleteCallback_; // 消息发送完成以后的回调
    HighWaterMarkCallback highWaterMarkCallback_;
    CloseCallback closeCallback_;
    size_t highWaterMark_;

    Buffer inputBuffer_;  // 接收数据的缓冲区
    Buffer outputBuffer_; // 发送数据的缓冲区
};

在 TcpConnection 的构造函数中,会创建一个 Channel ,并设置 Channel 的回调函数,需要注意的是,这里是绑定的是 TcpConnection 的 this 指针:

TcpConnection::TcpConnection(EventLoop *loop, 
                const std::string &nameArg, 
                int sockfd,
                const InetAddress& localAddr,
                const InetAddress& peerAddr)
    : loop_(CheckLoopNotNull(loop))
    , name_(nameArg)
    , state_(kConnecting)
    , reading_(true)
    , socket_(new Socket(sockfd))
    , channel_(new Channel(loop, sockfd))
    , localAddr_(localAddr)
    , peerAddr_(peerAddr)
    , highWaterMark_(64*1024*1024) // 64M
{
    // 下面给channel设置相应的回调函数,poller给channel通知感兴趣的事件发生了,channel会回调相应的操作函数
    channel_->setReadCallback(
        std::bind(&TcpConnection::handleRead, this, std::placeholders::_1)
    );
    channel_->setWriteCallback(
        std::bind(&TcpConnection::handleWrite, this)
    );
    channel_->setCloseCallback(
        std::bind(&TcpConnection::handleClose, this)
    );
    channel_->setErrorCallback(
        std::bind(&TcpConnection::handleError, this)
    );

    LOG_INFO("TcpConnection::ctor[%s] at fd=%d\n", name_.c_str(), sockfd);
    socket_->setKeepAlive(true);
}

当建立一个 Tcp 连接时, Channel 会通过 tie 方法弱引用绑定到 shared_ptr<TcpConnection> 对象:

// 连接建立
void TcpConnection::connectEstablished()
{
    setState(kConnected);
    channel_->tie(shared_from_this());
    channel_->enableReading(); // 向poller注册channel的epollin事件

    // 新连接建立,执行回调
    connectionCallback_(shared_from_this());
}

在 Channel 对象里,持有一个弱引用  std::weak_ptr<void> tie_ , 他的最主要作用是防止 Channel 对象本身会remove掉之后,还在执行回调操作:

class EventLoop;

/**
 * 理清楚  EventLoop、Channel、Poller之间的关系   《= Reactor模型上对应 Demultiplex
 * Channel 理解为通道,封装了sockfd和其感兴趣的event,如EPOLLIN、EPOLLOUT事件
 * 还绑定了poller返回的具体事件
 */ 
class Channel : noncopyable
{
public:
    using EventCallback = std::function<void()>;
    using ReadEventCallback = std::function<void(Timestamp)>;

    Channel(EventLoop *loop, int fd);
    ~Channel();

    // fd得到poller通知以后,处理事件的
    void handleEvent(Timestamp receiveTime);  

    // 设置回调函数对象
    void setReadCallback(ReadEventCallback cb) { readCallback_ = std::move(cb); }
    void setWriteCallback(EventCallback cb) { writeCallback_ = std::move(cb); }
    void setCloseCallback(EventCallback cb) { closeCallback_ = std::move(cb); }
    void setErrorCallback(EventCallback cb) { errorCallback_ = std::move(cb); }

    // 防止当channel被手动remove掉,channel还在执行回调操作
    void tie(const std::shared_ptr<void>&);

    int fd() const { return fd_; }
    int events() const { return events_; }
    void set_revents(int revt) { revents_ = revt; }

    // 设置fd相应的事件状态
    void enableReading() { events_ |= kReadEvent; update(); }
    void disableReading() { events_ &= ~kReadEvent; update(); }
    void enableWriting() { events_ |= kWriteEvent; update(); }
    void disableWriting() { events_ &= ~kWriteEvent; update(); }
    void disableAll() { events_ = kNoneEvent; update(); }

    // 返回fd当前的事件状态
    bool isNoneEvent() const { return events_ == kNoneEvent; }
    bool isWriting() const { return events_ & kWriteEvent; }
    bool isReading() const { return events_ & kReadEvent; }

    int index() { return index_; }
    void set_index(int idx) { index_ = idx; }

    // one loop per thread
    EventLoop* ownerLoop() { return loop_; }
    void remove();
private:

    void update();
    void handleEventWithGuard(Timestamp receiveTime);

    static const int kNoneEvent;
    static const int kReadEvent;
    static const int kWriteEvent;

    EventLoop *loop_; // 事件循环
    const int fd_;    // fd, Poller监听的对象
    int events_; // 注册fd感兴趣的事件
    int revents_; // poller返回的具体发生的事件
    int index_;

    std::weak_ptr<void> tie_;
    bool tied_;

    // 因为channel通道里面能够获知fd最终发生的具体的事件revents,所以它负责调用具体事件的回调操作
    ReadEventCallback readCallback_;
    EventCallback writeCallback_;
    EventCallback closeCallback_;
    EventCallback errorCallback_;
};

tie方法如下:

// 强弱智能指针的应用之一
// channel的tie方法什么时候调用过?一个TcpConnection新连接创建的时候 TcpConnection => Channel 
void Channel::tie(const std::shared_ptr<void> &obj)
{
    tie_ = obj;
    tied_ = true;
}

方法的形参是一个 shared_ptr ,使用一个 weak_ptr 来绑定一个强类型指针,另外使用一个变量记录了当前对象是否被绑定。这样在执行回调操作时,如果有绑定,则将弱指针转换为强类型指针,如果转换成功,则表示被绑定的对象还存在,则继续执行回到;如果转换失败,则表示被绑定的对象已经被释放,不进行任何操作:

// fd得到poller通知以后,处理事件的
void Channel::handleEvent(Timestamp receiveTime)
{
    if (tied_)
    {
        std::shared_ptr<void> guard = tie_.lock();
        if (guard)
        {
            handleEventWithGuard(receiveTime);
        }
    }
    else
    {
        handleEventWithGuard(receiveTime);
    }
}

// 根据poller通知的channel发生的具体事件, 由channel负责调用具体的回调操作
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
    LOG_INFO("channel handleEvent revents:%d\n", revents_);

    if ((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN))
    {
        if (closeCallback_)
        {
            closeCallback_();
        }
    }

    if (revents_ & EPOLLERR)
    {
        if (errorCallback_)
        {
            errorCallback_();
        }
    }

    if (revents_ & (EPOLLIN | EPOLLPRI))
    {
        if (readCallback_)
        {
            readCallback_(receiveTime);
        }
    }

    if (revents_ & EPOLLOUT)
    {
        if (writeCallback_)
        {
            writeCallback_();
        }
    }
}

参考: