在《Linux多线程服务器编程:使用muduo C++网络库》这本书中,作者在第15页一针见血的指出C++可能出现的内存问题的几个方面:
- 缓冲区溢出(buffer overrun)
- 空悬指针(dangling pointer 指向已销毁的对象或已回收的地址)/野指针(wild pointer 指的是未经初始化的指针)
- 重复释放(double delete)
- 内存泄漏(memory leak)
- 不配对的new[]/delete
- 内存碎片(memory fragmentation)
正确的使用智能指针可以解决前5个问题:
- 缓冲区溢出:使用STL中的容器(
std::vector
、std::string
)、使用安全的拷贝函数,或者自己编写buffer class来管理缓冲区,自动记住缓冲区的长度,并通过成员函数,而不是裸指针来修改缓冲区 - 空悬指针/野指针:用
std::shared_ptr
/std::weak_ptr
- 使用
std::unique_ptr
,只在对象析构时释放一次 - 使用
std::unique_ptr
,对象析构的时候自动释放 - 不配对的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_ptr
,B
对象持有一个指向 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_();
}
}
}
参考: