0%

weakcallback

WeakCallback是nbase库的一个重要部分,主要用来检测对象的生命周期。下面结合源码分析他是如何检测生命周期以及其缺陷。

SupportWeakCallback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class BASE_EXPORT SupportWeakCallback
{
public:
typedef std::weak_ptr<WeakFlag> _TyWeakFlag;
public:
virtual ~SupportWeakCallback(){};

template<typename CallbackType>
auto ToWeakCallback(const CallbackType& closure)
->WeakCallback<CallbackType>
{
return WeakCallback<CallbackType>(GetWeakFlag(), closure);
}

std::weak_ptr<WeakFlag> GetWeakFlag()
{
if (m_weakFlag.use_count() == 0) {
m_weakFlag.reset((WeakFlag*)NULL);
}
return m_weakFlag;
}

private:
template<typename ReturnValue, typename... Param, typename WeakFlag>
static std::function<ReturnValue(Param...)> ConvertToWeakCallback(
const std::function<ReturnValue(Param...)>& callback, std::weak_ptr<WeakFlag> expiredFlag)
{
auto weakCallback = [expiredFlag, callback](Param... p) {
if (!expiredFlag.expired()) {
return callback(p...);
}
return ReturnValue();
};

return weakCallback;
}

protected:
std::shared_ptr<WeakFlag> m_weakFlag;
};

class WeakFlag
{

};

SupportWeakCallback类的主要作用是提供被检测生命周期的功能,如果一个类需要被监控(例如UI窗口),则继承这个类即可。检测方法主要通过智能指针shared_ptr实现。成员变量中有个WeakFlag类型的shared指针,成员方法将其对应的weak指针暴露出来。这样子,持有weak指针的一方就可以在任何时候通过weak_ptr::expired方法判断对应shared指针是否存在,从而检测到对象的生命周期。WeakFlag是一个空类,占位使用。

WeakCallback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
template<typename T>
class WeakCallback
{
public:
WeakCallback(const std::weak_ptr<WeakFlag>& weak_flag, const T& t) :
weak_flag_(weak_flag),
t_(t)
{

}

WeakCallback(const std::weak_ptr<WeakFlag>& weak_flag, T&& t) :
weak_flag_(weak_flag),
t_(std::move(t))
{

}

template<class WeakType>
WeakCallback(const WeakType& weak_callback) :
weak_flag_(weak_callback.weak_flag_),
t_(weak_callback.t_)
{

}

template<class... Args>
auto operator ()(Args && ... args) const
->decltype(t_(std::forward<Args>(args)...))
{
if (!weak_flag_.expired()) {
return t_(std::forward<Args>(args)...);
}
return decltype(t_(std::forward<Args>(args)...))();
}

bool Expired() const
{
return weak_flag_.expired();
}


std::weak_ptr<WeakFlag> weak_flag_;
mutable T t_;
};

WeakCallback可以将一个对象跟一个weak指针绑定在一起,在实际应用中主要配合可执行对象使用。它实现了operator ()函数,当weak指针所绑定的对象存在时,才执行函数。

Bind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// global function 
template<class F, class... Args, class = typename std::enable_if<!std::is_member_function_pointer<F>::value>::type>
auto Bind(F && f, Args && ... args)
->decltype(std::bind(f, args...))
{
return std::bind(f, args...);
}

// const class member function
template<class R, class C, class... DArgs, class P, class... Args>
auto Bind(R(C::*f)(DArgs...) const, P && p, Args && ... args)
->WeakCallback<decltype(std::bind(f, p, args...))>
{
std::weak_ptr<WeakFlag> weak_flag = ((SupportWeakCallback*)p)->GetWeakFlag();
auto bind_obj = std::bind(f, p, args...);
static_assert(std::is_base_of<nbase::SupportWeakCallback, C>::value, "nbase::SupportWeakCallback should be base of C");
WeakCallback<decltype(bind_obj)> weak_callback(weak_flag, std::move(bind_obj));
return weak_callback;
}

// non-const class member function
template<class R, class C, class... DArgs, class P, class... Args>
auto Bind(R(C::*f)(DArgs...), P && p, Args && ... args)
->WeakCallback<decltype(std::bind(f, p, args...))>
{
std::weak_ptr<WeakFlag> weak_flag = ((SupportWeakCallback*)p)->GetWeakFlag();
auto bind_obj = std::bind(f, p, args...);
static_assert(std::is_base_of<nbase::SupportWeakCallback, C>::value, "nbase::SupportWeakCallback should be base of C");
WeakCallback<decltype(bind_obj)> weak_callback(weak_flag, std::move(bind_obj));
return weak_callback;
}

继承了SupportWeakCallback的类,应该使用nbase::Bind而非std::Bind。因为nbase::Bind能生成WeakCallback的可执行对象,比std::Bind多了一个生命周期的检测,更加安全。对于全局函数nbase::Bind退化成std::Bind

WeakCallbackFlag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class BASE_EXPORT WeakCallbackFlag final : public SupportWeakCallback
{
public:
void Cancel()
{
m_weakFlag.reset();
}

bool HasUsed()
{
return m_weakFlag.use_count() != 0;
}
};

SupportWeakCallback一般用于类的继承,而WeakCallbackFlag一般用在成员变量。SupportWeakCallback只有在析构的时候才会销毁对应的shared指针,但WeakCallbackFlag可以通过主动调用Cancel销毁,比SupportWeakCallback更轻便。在实际应用中主要配合nbase::ThreadManager::PostRepeatedTask当定时器使用

缺点

WeakCallback的优点在于,它提供一种比较优雅的方式去检测对象的生命周期;但是缺点在于,它并不是线程安全的。比较典型的例子是这样的

1
2
3
4
5
6
7
8
9
10
11
// 继承了SupportWeakCallback类的成员函数
void classSupportWeakCallbackFunc(){
auto http_cb = ToWeakCallback([=](bool ret, int response_code, const std::string &reply){
QLOG_APP(L"baidu result: Ret:{0}, Code:{1}, Json:{2}") << ret << response_code << reply;
StdClosure cb = ToWeakCallback([=](){
// update ui
};
Post2UI(cb);
}));
HttpHandler::Get(http_cb, "www.baidu.com");
}

当初我在项目里很容易就把ToWeakCallback顺手写进去了,但是不对的。问题就在第二个ToWeakCallback里。它是在Http线程里被调用,并且没有锁操作。因此有可能在执行第一个ToWeakCallback的时候this是存在的,但是在生成第二个ToWeakCallback的时候已经销毁。正确的写法是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
// 继承了SupportWeakCallback类的成员函数
void classSupportWeakCallbackFunc(){
auto weak_flag = this->GetWeakFlag();
auto http_cb = ToWeakCallback([this, weak_flag](bool ret, int response_code, const std::string &reply){
QLOG_APP(L"baidu result: Ret:{0}, Code:{1}, Json:{2}") << ret << response_code << reply;
StdClosure cb = [=](){
if(weak_flag.expried()) return;
// update ui
};
Post2UI(cb);
});
HttpHandler::Get(http_cb, "www.baidu.com");
}

在http线程不操作this,而是传递weak指针,留到UI线程再去判断。