QtPromise源码剖析-CPP模板元编程

目录

Promise概念

QtPromise开源模板库

QtPromise模板库中所使用的设计模式

 1. QtPromise中的构建模式

 2. QtPromise中的装饰模式

QtPromise模板库中使用到的元编程技巧

1. 模板元函数

2. 全特化&偏特化构建if-then-else

3. Type Traits 类型特征

4. SFINAE

QtPromise源码分析和实现思想

1. QtPromise的UML 时序图

2. QtPromise源码分析

设计模式与元编程的混合

1. 元编程与单例模式sampleCode

2. 元编程与其他模式


Promise概念

Promise是一种异步编程的解决方案. 

Promises 是用于传递异步计算结果的回调的替代方法.

QtPromise开源模板库

使用Qt框架的朋友如果对异步编程有需求,建议可使用此模板库对异步操作做处理。

github地址: GitHub - simonbrunel/qtpromise: Promises/A+ implementation for Qt/C++

使用手册地址: Getting Started | QtPromise

下文对QtPromise模板库的源码做一些分析以及其所用到的一些CPP的技巧共同做些探讨。

本文并不会教你如何更好的使用QPromise而是分享其内部实现的思想和流程。

QtPromise模板库中所使用的设计模式

 1. QtPromise中的构建模式

 a. 一般我们在构造复杂对象时为了简化初始化通常会把多个成员属性拆分成多个set/with成员方法来用于构造对象。

 b. 同样为了让QPromise的调用更加符合Promise风格和达到链式调用的目的。QPromise类对外接口也都是基于此模式的变种。

  QPromise 父类QPromiseBase源码示例:

// 摘自QPromise类的部分成员函数声明
template
class QPromiseBase {
    template
    inline typename QtPromisePrivate::PromiseHandler::Promise
    then(const TFulfilled& fulfilled, const TRejected& rejected) const;

    template
    inline typename QtPromisePrivate::PromiseHandler::Promise
    then(TFulfilled&& fulfilled) const;

    template
    inline typename QtPromisePrivate::PromiseHandler::Promise
    fail(TRejected&& rejected) const;

    template
    inline QPromise finally(THandler handler) const;

    template
    inline QPromise tap(THandler handler) const;

    template
    inline QPromise tapFail(THandler handler) const;

    template
    inline QPromise timeout(int msec, E&& error = E{}) const;

    template
    inline QPromise timeout(std::chrono::milliseconds msec, E&& error = E{}) const;

    inline QPromise delay(int msec) const;
    inline QPromise delay(std::chrono::milliseconds msec) const;

    inline QPromise wait() const;
};

我们看到QPromiseBase大部分函数都返回了QPromise实例对象符合构建模式的思想但与构建模式又存在一定区别,构建模式的设置方法是作用于同一对象且返回的也是同一对象。 但QPromiseBase中每次返回是一个新的Promise实例对象。

 2. QtPromise中的装饰模式

a. 业务逻辑请使用组合方式来实现。 -- 摘自微服务架构设计模式一书

组合模式带来的好处:

  • 类型间更弱的耦合
  • 运行期灵活性更好,能够根据业务需要随时替换组合对象
  • 对于客户代码更加友好

b. 基于PromiseResolver的装饰

我们先来看看PromiseResolver类的源码


template
class PromiseResolver
{
public:
    PromiseResolver(QtPromise::QPromise promise) : m_d{new Data{}}
    {
        m_d->promise = new QtPromise::QPromise{std::move(promise)};
    }

    template
    void reject(E&& error)
    {
        auto promise = m_d->promise;
        if (promise) {
            Q_ASSERT(promise->isPending());
            promise->m_d->reject(std::forward(error));
            promise->m_d->dispatch();
            release();
        }
    }

    void reject()
    {
        auto promise = m_d->promise;
        if (promise) {
            Q_ASSERT(promise->isPending());
            promise->m_d->reject(QtPromise::QPromiseUndefinedException{});
            promise->m_d->dispatch();
            release();
        }
    }

    template
    void resolve(V&& value)
    {
        auto promise = m_d->promise;
        if (promise) {
            Q_ASSERT(promise->isPending());
            promise->m_d->resolve(std::forward(value));
            promise->m_d->dispatch();
            release();
        }
    }

    void resolve()
    {
        auto promise = m_d->promise;
        if (promise) {
            Q_ASSERT(promise->isPending());
            promise->m_d->resolve();
            promise->m_d->dispatch();
            release();
        }
    }

private:
    struct Data : public QSharedData
    {
        QtPromise::QPromise* promise = nullptr;
    };

    QExplicitlySharedDataPointer m_d;

    void release()
    {
        Q_ASSERT(m_d->promise);
        Q_ASSERT(!m_d->promise->isPending());
        delete m_d->promise;
        m_d->promise = nullptr;
    }
};

//

template
class QPromiseResolve
{
public:
    QPromiseResolve(QtPromisePrivate::PromiseResolver resolver) : m_resolver{std::move(resolver)}
    {
        qDebug() << "QPromiseResolve";
    }


    ~QPromiseResolve() {
        qDebug() << "~QPromiseResolve";
    }

    template
    void operator()(V&& value) const
    {
        m_resolver.resolve(std::forward(value));
    }

    void operator()() const { m_resolver.resolve(); }

private:
    mutable QtPromisePrivate::PromiseResolver m_resolver;
};

template
class QPromiseReject
{
public:
    QPromiseReject(QtPromisePrivate::PromiseResolver resolver) : m_resolver{std::move(resolver)}
    { }

    template
    void operator()(E&& error) const
    {
        m_resolver.reject(std::forward(error));
    }

    void operator()() const { m_resolver.reject(); }

private:
    mutable QtPromisePrivate::PromiseResolver m_resolver;
};

从源码分析我们知道PromiseResolver的功能比较简单单一,持有一个QPromise实例, 对客户代码提供操作成功和失败的方法进而把外部操作结果转发到持有的promise实例内部处理。

QPromise为了达到更加简单易用的方式(更加符合现代的函数式编程范式)以及语义上更加符合Promise风格,继续定义了QPromiseResolve&QPromiseReject类用来单独处理成功和失败的情况。 我们看这三者类并不是教科书上的标准的装饰模式, 但实际上QPromiseResolve&QPromiseReject类的实现组合PromiseResolver实例, 重载调用()运算符(使其成为一个可调用对象)内部转发对PromiseResolver的调用基本符合了装饰模式的思想只是缺少了基类接口的存在,对外缺少统一调用接口但()操作符的重载很好的弥补了这一缺陷。

PromiseResolver&QPromiseResolve&QPromiseReject UML 类图

QtPromise源码剖析-CPP模板元编程_第1张图片

 

从Promise的使用情况看, 对于客户代码只需要关心在处理结果时对于QPromiseResolve&QPromiseReject调用操作,由于QPromiseResolve&QPromiseReject类组合PromiseResolver,持有QPromise对象且存在整个QPromise执行链中。个人在这将PromiseResolver&QPromiseResolve&QPromiseReject 统一称作QPromise上下文类。

QtPromise模板库中使用到的元编程技巧

由于QtPromise是一个轻量级的模板库,内部用到了大量模板元技术。 故在分析QtPromise源码前我们需对QtPromise中用到的CPP模板元技巧概念做些介绍

1. 模板元函数

   a. 元函数函数特征&定义

  • 元函数并不是一个传统的语言函数,它通常是一个struct或class.
  • 通常返回一个或多个类型
  • 元函数并不是语言的一部分也没有正式的语言支持它
  • 它们作为现有语言功能的惯用用法而存在
  • 它们的使用不是由语言强制执行的

   b. 值元函数&型别元函数

     值元函数通常返回一个值     

template
class ValueMetaFunction {
    static constexpr int value = 100;
};

    我们声明&定义了一个名为ValueMetaFunction的元函数,返回了一个value为int的值.

    值元函数的plus版本

template
class ValueMetaFunction {
    static constexpr auto value = 100;
};

template
class ValueMetaFunction {
    static constexpr T value = value
};

 型别元函数通常返回一个类型

template
class TypeMetaFunction {
    using type = T;
};

声明&定义一个名为TypeMetaFunction的元函数,返回了一个型别为T的type类型

c. 元函数的调用

针对于上述的示例代码我们现在来获取元函数的返回值

ValueMetaFunction::value
ValueMetaFunction::value
TypeMetaFunction::type  --> typename TypeMetaFunction::type

我们在调用元函数返回值时一般书写都比较长,阅读起来也比较不友好。CPP提供了using关键字可以用于定义更加便捷的调用方式。

使用using关键字为模板(元函数)定义别名。

通常value元函数使用以 _v结尾的变量模板

template
using value_v = ValueMetaFunction::value

通常type 元函数使用以 _t结尾的变量模板

template
using type_t = typename ValueMetaFunction::type

使用便捷的调用方式

value_v
type_t

d. 一个有用的元函数

// STRUCT TEMPLATE integral_constant
template 
struct integral_constant {
    static constexpr _Ty value = _Val;

    using value_type = _Ty;
    using type       = integral_constant;

    constexpr operator value_type() const noexcept {
        return value;
    }

    _NODISCARD constexpr value_type operator()() const noexcept {
        return value;
    }
};

// ---摘自MSVC xtr1commom文件

integral_constant应用于在系统位数判断上

static const std::uint64_t default_buffer_size = 
std::conditional,   
                 std::integral_constant
                >::type::value

这样编码的优势:

  1. integral_constant、sizeof(void *) == 8 数值等都在编译期执行计算不占用运行时开销。
  2. default_buffer_size 可在编译时优化成一个编译常量控件在编译时就完成分配,提升程序运行时效率。

2. 全特化&偏特化构建if-then-else

模板的全特化与偏特化特性构建出了元编程中的条件判断

模板偏特化&全特换的示例:

// 标准模板
template
struct Sample {};

// 对于T为指针的偏特化
template
struct Sample {};

// 对于T为int的偏特化
template
struct Sample {};

// 对于T&U都为int类型的全特化 
template<>
struct Sample {};

声明if-then-else:

template< bool CONDITION, class THEN, class ELSE > struct IF {};

template struct IF< false, THEN, ELSE > {typedef ELSE TEST;};

template struct IF< true, THEN, ELSE > {typedef THEN TEST;};

if-then-else SampleCode:

template< bool Condition >
class IF {
public:
    static inline void EXEC(){std::cout << "Statement is true";}
};

// 全特化IF模板,当模板参数为false时匹配该版本
class IF< false > {
public:
    static inline void EXEC(){std::cout << "Statement is false";}
};

std::cout << IF::EXEC()<< std::endl;

3. Type Traits 类型特征

a. type traits是什么?

  • 它是C++泛型编程的支柱之一
  • 检查与转换类型的属性
  • 它通常是一个简单的模板结构

b. MSVC中的type_traits文件中定义了大量常用的类型特征模板

 下图摘自 - C++ Reference部分

QtPromise源码剖析-CPP模板元编程_第2张图片

 QtPromise源码剖析-CPP模板元编程_第3张图片

 具体说明及用法大家可以点击链接进行查看。上述类型特性模板在常见的模板设计程序中会经常有使用到。

c. call_traits

示例

// CLASS TEMPLATE binder1st
template 
class binder1st : public unary_function { // functor adapter _Func(stored, right)
public:
    using _Base         = unary_function;
    using argument_type = typename _Base::argument_type;
    using result_type   = typename _Base::result_type;

    binder1st(const _Fn& _Func, const typename _Fn::first_argument_type& _Left) : op(_Func), value(_Left) {}

    // @1
    result_type operator()(const argument_type& _Right) const {
        return op(value, _Right);
    }

    result_type operator()(argument_type& _Right) const {
        return op(value, _Right);
    }

protected:
    _Fn op;
    typename _Fn::first_argument_type value; // the left operand
};


// -- 摘自MSVC functional头文件

MSVC实现的binder1st 结构中存在着如果代码段@1中 argument_type如果是个引用类型,则binder1st重载()调用运算符存在引用引用的的问题。 这在C++中是不被允许的

call_traits 工具 -- 摘自boost程序库

call_traits 的目的是确保像“引用引用”这样的问题永远不会发生,并且以最有效的方式传递参数。

template 
struct ct_imp2 {
    typedef const T& param_type;
};

template 
struct ct_imp2 {
    typedef const T param_type;
};

template 
struct ct_imp {
    typedef const T& param_type;
};

template 
struct ct_imp {
    typedef typename ct_imp2::param_type param_type;
};

template 
struct ct_imp {
    typedef typename ct_imp2::param_type param_type;
};

template 
struct ct_imp {
    typedef const T param_type;
};


template 
struct call_traits
{
public:
    typedef T value_type;
    typedef T& reference;
    typedef const T& const_reference;
    typedef typename ct_imp<
        T,
        ::boost::is_pointer::value,
        ::boost::is_arithmetic::value,
        ::boost::is_enum::value
    >::param_type param_type;
};

template 
struct call_traits
{
    typedef T& value_type;
    typedef T& reference;
    typedef const T& const_reference;
    typedef T& param_type;  // hh removed const
};
template 
struct call_traits
{
    typedef T& value_type;
    typedef T& reference;
    typedef const T& const_reference;
    typedef T& param_type;  // hh removed const
};


// 摘自boost程序库 call_traits.hpp 部分

binder1st 对于调用操作符的重载最终可修改为

result_type operator()(typename call_traits::param_type _Right) const {
    return op(value, _Right);
}

b. QtPromise中的型别特性的应用

QtPromise中定义了ArgsTraits元函数用于提取函数的形参列表元信息。

template
struct ArgsTraits
{
    using types = std::tuple;
    // 返回函数参数列表第一个形参的类型
    using first = typename std::tuple_element<0, types>::type;
    // 返回函数参数列表的形参数量
    static const size_t count = std::tuple_size::value;
};

// 全特化参数列表为空的情况
template<>
struct ArgsTraits<>
{
    using types = std::tuple<>;
    using first = void;
    static const size_t count = 0;
};


// -- 摘自 qpromiseglobal.h 头文件

   定义ArgsTraits的扩展类ArgsOf元函数,由ArgsTraits的声明可知,ArgsTraits元函数接收的是有个类型参数包。 扩展类ArgsOf的作用则用于收集函数列表类型信息。


// 标准的ArgsOf类型的声明定义
template  struct ArgsOf : public ArgsTraits<> { };

// 对nullptr_t的偏特化
template<> struct ArgsOf : public ArgsTraits<>{ };

// 对operator () 成员函数指针偏特化
template 
struct ArgsOf::value>::type>: public ArgsOf { };

// 对引用类型的偏特化
template struct ArgsOf : public ArgsOf { };
// 对右值类型的偏特化
template struct ArgsOf : public ArgsOf { };

// 对可调用类型的偏特化
template struct ArgsOf : public ArgsTraits { };

// 对函数指针的偏特化
template struct ArgsOf : public ArgsTraits { };

// 对成员函数指针偏特化
template struct ArgsOf : public ArgsTraits { };

// 对const的成员函数指针偏特化 @7
template struct ArgsOf : public ArgsTraits { };

// 对volatile的成员函数指针偏特化 @8
template struct ArgsOf : public ArgsTraits { };

// 偏特化类型T为 const volatile的成员函数指针 @9
template struct ArgsOf : public ArgsTraits { };

// -- 摘自 qpromiseglobal.h 头文件

我们看到ArgsOf对于大部分函数指针类型的情况都进行了偏特化从而对特定的函数形参列表参数包进行提取,进而传递给其父类。完成对函数形参列表信息的提取。

我们其实发现ArgsOf作用紧紧是用于提取函数的形参列表类型,至于类的成员函数是否是 const、volatile、const volatile等类型是不关心的。但为了满足类的成员函数所有类型的提取则必须尽可能的偏特化出所有成员函数类型的ArgsOf版本 见代码段@7& 8 & 9。细心的读者可能会发现ArgsOf对于类的成员函数形参列表类型的元信息提取其实是不全的,因为ArgsOf并没有偏特化全部的成员函数类型。例如 const&、const&&、const volatile&、const volatile&&、volatile&、volatile&&成员函数类型就没有进行偏特化, 对于这一类型的成员函数类型在编译器实例化ArgsOf时会去匹配标准的ArgsOf版本所以函数形参列表会作为空处理。

如果你的项目中有上述提到的未偏特化的成员函数则在使用QPromise时就出现错误了。 我们该如何去优化以下ArgsOf呢?

项目开发中我们可以提供一个基础的大而全的type_traits, 用于提取裸函数类型。 无论你是否使用QtPromise模板库, 至少在项目其他需求中也是可以作为一个基础类而存在的。

提供一个is_const_or_volatile_member_function 成员函数类型特征工具类, 声明定义如下:

template 
struct is_const_or_volatile_member_function : std::false_type { };

template 
struct is_const_or_volatile_member_function : std::true_type{
    using type = R(T::*)(Args...);
};
template 
struct is_const_or_volatile_member_function : std::true_type {
    using type = R(T::*)(Args...);
};
template 
struct is_const_or_volatile_member_function : std::true_type {
    using type = R(T::*)(Args...);
};
template 
struct is_const_or_volatile_member_function : std::true_type {
    using type = R(T::*)(Args...);
};
template 
struct is_const_or_volatile_member_function : std::true_type {
    using type = R(T::*)(Args...);
};
template 
struct is_const_or_volatile_member_function : std::true_type {
    using type = R(T::*)(Args...);
};
template 
struct is_const_or_volatile_member_function : std::true_type {
    using type = R(T::*)(Args...);
};
template 
struct is_const_or_volatile_member_function : std::true_type {
    using type = R(T::*)(Args...);
};
template 
struct is_const_or_volatile_member_function : std::true_type {
    using type = R(T::*)(Args...);
};

is_const_or_volatile_member_function偏特化出了成员函数的所有的类型, 且继承与std::false_type or std::true_type用于给出一个bool值判断传入的类型是否为成员函数类型。最终返回一个裸成员函数指针类型type(此处裸代表 成员函数是非 const、 const&、const&&、const volatile、const volatile&、const volatile&&、volatile、volatile&、volatile&& 类型)

is_const_or_volatile_member_function使用:

这里对于之前ArgsOf定义的代码段@7、8、9 统一可以修改为下图的代码段@2。 这样可以由is_const_or_volatile_member_function元函数去提取所有的非裸成员函数类型也节省了ArgsOf偏特化的版本数量。

// @1
// 对指针成员函数类型的偏特化
template
struct ArgsOf : public ArgsTraits
{
    static constexpr char* value = "R(T::*)(Args...)";
};

// @2
// 对 const、 const&、const&&、const volatile、const volatile&、
// const volatile&&、volatile、volatile&、volatile&& 成员函数指针偏特化
template
struct ArgsOf ::value> >: 
    public ArgsOf::type>
{
    static constexpr char* value = "R(*)(Args...) const or volatile";
};

 此处我们来验证下对于优化后ArgsOf的使用

use sample code: 


qDebug() << ArgsOf::value;
qDebug() << ArgsOf::value;
qDebug() << ArgsOf::value;
qDebug() << ArgsOf::value;
qDebug() << ArgsOf::value;
qDebug() << ArgsOf::value;

 输出:

R(T::*)(Args...)
R(*)(Args...) const or volatile
R(*)(Args...) const or volatile
R(*)(Args...) const or volatile
R(*)(Args...) const or volatile
R(*)(Args...) const or volatile

 从输出我们可以分析出非裸成员函数指针类型都匹配到上述代码段@2的ArgsOf偏特化版本,裸成员函数类型匹配到了代码段@1的ArgsOf偏特化版本。至此我们完成了对QtPromise模板库中ArgsOf的偏特化类的成员函数类型不足的优化。

4. SFINAE

a. SFINAE 是什么?

  • 替换失败不是错误(Substitution failure is not an error)
  • 如果你重载一个函数并调用它,只要有一个重载是有效的。其他重载可以默认失败, 在所有重载函数选择最佳匹配。
  • 对于需要重载多个函数是非常有用的

b. SFINAE 能做什么?

  • 检查一个类中是否含有成员XXMember
  • 检查一个结构中是否定义函数XXFunction
  • 一个类型能做什么
  • 编译时的布尔值检查/条件编译 

 c. SFINAE在QtPromise中的应用

  检查一个类中是否函数成员函数

template
struct HasCallOperator 
{
    template
    static char check(decltype(&U::operator(), char(0)));

    template
    static char (&check(...))[2];
    static const bool value = (sizeof(check(0)) == 1);
};

// -- 摘自qpromiseglobal.h文件

 QPromiseBase构造函数重载

template::count == 1, int>::type = 0>
inline QPromiseBase(F resolver);
template::count != 1, int>::type = 0>
inline QPromiseBase(F resolver);

根据实参类型中的形参列表数量编译器来进行最佳的构造函数生成。 

QtPromise源码分析和实现思想

1. QtPromise的UML 时序图

QtPromise源码剖析-CPP模板元编程_第4张图片 

 

下面我结合此时序图对QtPromise 源码的主要流程进行分析

2. QtPromise源码分析

a. QPromise的构造&生命周期

先看一个基本的使用QPromise Sample Code:

{
    QPromise promise = QPromise([](QPromiseResolve resolve, QPromiseReject reject) {
        // @1
        resolve(100)
    }).then([](int) -> std::string {
        return "100";
    });
}

 上述代码段构建了一个基本的Promise链式调用并返回链式最后一个QPromise实例,不知道大家在使用QPromise时有没有这样的一个疑问,就是在上述代码段执行完成后promise实例都销毁了。 它是如何保证异步继续执行下有个then方法函数体的??? 随着我们对QPromise对象的构造分析慢慢来解答这个问题。

QPromise构造函数分析

template
class QPromiseBase
{
public:
    using Type = T;

    // @1
    template::count == 1, int>::type = 0>
    inline QPromiseBase(F resolver) :  m_d{new QtPromisePrivate::PromiseData{}} 
    {
        // @6
        QtPromisePrivate::PromiseResolver resolver{*this};

        try {
            // @4
            callback(QPromiseResolve(resolver));
        } catch (...) {
            resolver.reject(std::current_exception());
        }
    }

    // @2
    template::count != 1, int>::type = 0>
    inline QPromiseBase(F resolver) : m_d{new QtPromisePrivate::PromiseData{}} 
    {
        // @7
        QtPromisePrivate::PromiseResolver resolver{*this};

        try {
            // @5
            callback(QPromiseResolve(resolver), QPromiseReject(resolver));
        } catch (...) {
            resolver.reject(std::current_exception());
        }
    }
};

template
class QPromise : public QPromiseBase
{
public:
    // @3
    template
    QPromise(F&& resolver) : QPromiseBase(std::forward(resolver)) {
    }
};

  1. QPromise构造函数接收一个任意实参(形参类型为万能引用类型),但通常为一个可调用对象(函数指针、成员函数、Lambda表达式、重载()运算符的类、std::function). 从上图代码段@4&5中我们不难发现此规则, 且该调用对象的形参数量必须为1或者2 , 为1时形参类型必须为QPromiseResolve、为2是形参类型为QPromiseResolve&QPromiseReject。
  2. QPromise将万能引用形参resolver作为实参转发到父类QPromiseBase的构造函数上。上图代码段@3 对万能引用类型使用std::forward进行转发(参考effective modern C++)。
  3. QPromiseBase存在代码段@1&2 两个模板函数重载版本 (参考上文的SFINA),最终选择哪个版本则根据上文介绍的ArgsOf类型特性模板来提取类型F的参数列表数量,如果为1则匹配代码段@1否则匹配代码段@2。 
  4. QPromiseBase函数体实现:代码段@6&7 构造上下文类PromiseResolver局部对象, 将自己的控制权交由PromiseResolver对象 。基于PromiseResolver对象构造QPromiseResolve 或 QPromiseResolve&QPromiseReject(根据构造QPromise对象时的实参(可调用类型)的形参数量决定)。
  5. QPromise的构造过程中会立马对构造实参进行回调,进而进入到客户代码的函数栈中。 当客户代码调用形参1或者2 则相当于把操作结果交到的QPromise上下文类中。
  6. 我们此处仅对操作做成功的情况讨论其他流程基本一致,结合QPromise Sample Code(本节开头处的示例)进行流程梳理. 
  7.  当客户代码将操作结果100 通知到QPromiseResolve 对象, resolve对结果进行转发,进而调用装饰对象QPromiseResolver的resolve函数, 个人认为该方法没必要为模板方法,形参类型为T就行。
        template
        void resolve(V&& value)
        {
            auto promise = m_d->promise;
            if (promise) {
                Q_ASSERT(promise->isPending());
                promise->m_d->resolve(std::forward(value));
                promise->m_d->dispatch();
                release();
            }
        }

  8. promise->m_d->resolve(std::forward(value)); 将客户代码的操作结果存入到成员属性m_d
  9.  promise->m_d->dispatch();  把当前结果已异步的方式分发到下个待处理的QPromise对象
  10. release(); 释放当前上下文持有的QPromise堆对象,所以在QPromise构造函数中回调用户代码时,用户代码应该调用resolve或者reject 避免内存泄漏。

 相信看到此处我们知道QtPromise内部是如何保证其生命周期了。

  • 得益于PromiseResolver上下文对QPromise封装,其副本已保存在上下文类中,其生命周期的长短取决于客户代码对resolve与reject的调用。
  • 保证后续方法得以执行则是依赖Qt event runloop机制。 Event 封装可调用对象(内部为Lambda)基于异步事件实现异步链式调用。
  • 请保证在一定要执行resolve与reject可调用对象,不然会有内存泄漏。

QPromise then核心函数分析

then提供了接收下一步(异步)处理的函数入口(下文中称nextPromise对象)

then函数体源码

template
template
inline typename QtPromisePrivate::PromiseHandler::Promise
QPromiseBase::then(const TFulfilled& fulfilled, const TRejected& rejected) const
{
    using namespace QtPromisePrivate;
    // @1
    using PromiseType = typename PromiseHandler::Promise;
    // @2
    PromiseType next([&](const QPromiseResolve& resolve,
                         const QPromiseReject& reject) {
        // @3
        m_d->addHandler(PromiseHandler::create(fulfilled, resolve, reject));
        m_d->addCatcher(PromiseCatcher::create(rejected, resolve, reject));
    });

    if (!m_d->isPending()) {
        m_d->dispatch();
    }

    return next;
}

template
template
inline typename QtPromisePrivate::PromiseHandler::Promise
QPromiseBase::then(TFulfilled&& fulfilled) const
{
    return then(std::forward(fulfilled), nullptr);
}
  1.  then函数形参通常接收有个可调用对象
  2. 代码段@1 通过PromiseHandler型别特征提取当前待返回Promise的模板类型与返回类型  ,参考使用QPromise Sample Code示例,PromiseType为QPromise
  3. 构造局部对象QPromise 并立即执行实参Lambda表达式。
  4. 通过PromiseHandler 创建可调用对象(std::function)并将当前then的形参以及nextPromise下的上下文保存在该可调用对象中
    // PromiseHandler标准版
    template::first>
    struct PromiseHandler
    {
        using ResType = typename invoke_result::type;
        using Promise = typename PromiseDeduce::Type;
    
        template
        static std::function
        create(const THandler& handler, const TResolve& resolve, const TReject& reject)
        {
            return [=](const T& value) {
                PromiseDispatch::call(resolve, reject, handler, value);
            };
        }
    };
  5. 将PromiseHandler构建的可调用对象保存在当前的成员属性QPromiseData中

  6. 返回Promise对象继续以链式的方式接收下一个处理者

c. QPromiseData结构&Promise链式内存模型

QPromise 类从其父类PromiseBase继承了唯一的数据属性成员m_d。

QExplicitlySharedDataPointer> m_d;

QPromiseData结构, 下图源码仅给出了Promise成员属性,


template
class PromiseDataBase : public QSharedData {
public:
    using Handler = std::pair, std::function>;
    using Catcher = std::pair, std::function>;
protected:
    mutable QReadWriteLock m_lock;
    
private:
    bool m_settled = false;
    // 异步事件完成后下一步需要调用的可调用对象集合(及nextPromise的封装) 
    QVector m_handlers;
    // 出错处理情况
    QVector m_catchers;
    PromiseError m_error;

};


template
class PromiseData : public PromiseDataBase {
    using Handler = typename PromiseDataBase::Handler;
private:
    
    // 当前Promise处理(返回)的值交由PromiseValue自动管理其生命周期
    PromiseValue m_value;
};

// 全特化QPromise返回值类型为void的case
template<>
class PromiseData : public PromiseDataBase
{
    using Handler = PromiseDataBase::Handler;
}


// 对数据T使用RAII手法来管理其生命周期
template
class PromiseValue
{
public:
    PromiseValue() { }
    PromiseValue(const T& data) : m_data(QSharedPointer::create(data)) { }
    PromiseValue(T&& data) : m_data(QSharedPointer::create(std::forward(data))) { }
    bool isNull() const { return m_data.isNull(); }
    const T& data() const { return *m_data; }

private:
    QSharedPointer m_data;
};

从Promise类的属性成员 QPromiseData的结构我们可以给出其内存模型 

QtPromise源码剖析-CPP模板元编程_第5张图片 

QPromiseResolve&QPromiseReject为PromiseResolver的装饰对象。

PromiseResolver是一个携带QPromise对象的上下文,即handler与catcher callable对象中成员属性resolve、reject装饰对象分别又指向了QPromiseData对象即形成了QPromise 链式内存模式。 

d. QPromise 上下文类的作用

  • 封装QtPromise链式头对象,专注于首次结果。
  • 隔离客户代码与QPromise的关联,让客户代码调用更加简单只需要关心结果即可。
  • 转移QPromise对象生命周期,并由客户代码来管理QPromise生命周期。
  • 简化客户业务代码,分离成功与失败逻辑,依赖Qt显示共享技术。

e. Qt消息循环实现Promise非阻塞调用

template
static void qtpromise_defer(F&& f, const QPointer& thread)
{
    using FType = typename std::decay::type;

    struct Event : public QEvent
    {
        Event(FType&& f) : QEvent{QEvent::None}, m_f{std::move(f)} { }
        Event(const FType& f) : QEvent{QEvent::None}, m_f{f} { }
        ~Event() override { m_f(); }
        FType m_f;
    };

    if (!thread || thread->isFinished()) {
        // Make sure to not call `f` if the captured thread doesn't exist anymore,
        // which would potentially result in dispatching to the wrong thread (ie.
        // nullptr == current thread). Since the target thread is gone, it should
        // be safe to simply skip that notification.
        return;
    }

    QObject* target = QAbstractEventDispatcher::instance(thread);
    if (!target && QCoreApplication::closingDown()) {
        // When the app is shutting down, the even loop is not anymore available
        // so we don't have any way to dispatch `f`. This case can happen when a
        // promise is resolved after the app is requested to close, in which case
        // we should not trigger any error and skip that notification.
        return;
    }

    Q_ASSERT_X(target, "postMetaCall", "Target thread must have an event loop");
    QCoreApplication::postEvent(target, new Event{std::forward(f)});
}

Promise完成异步调用最终函数体,依赖Qt异步消息机制。 下一个待处理的可调用对象(Lambda表达式)封装着nextPromise的上下文对象。

F: HandlerCallable

Event: 自定义QEvent携带Callable基于消息循环异步调用nextPromise

thread: 保证线程单一性

 

设计模式与元编程的混合

对于喜欢元编程的读写朋友在这里推荐去阅读Loki 库的源码,其中对于元编程的各项技巧都有深入涉及。Loki主要使用模板元编程去实现通用的设计模式。看看模板元编程与设计模式在一起组合会碰撞出什么样的火花。 (#^.^#)

1. 元编程与单例模式sampleCode

策略结构的定义

对象创建策略结构

template  struct CreateUsingNew {
    static T* Create() {
        return new T;
    }
    static void Destroy(T* p) {
        delete p;
    }
};

对象生命周期策略结构

template 
struct DefaultLifetime {
    static void ScheduleDestruction(T*, atexit_pfn_t pFun) {
        std::atexit(pFun);
    }
    static void OnDeadReference() {
        throw std::logic_error("Dead Reference Detected");
    }
};

单例构造模板类的定义

QtPromise源码剖析-CPP模板元编程_第6张图片

 模板单例类接收两个默认的创建对象以及生命周期管理类型。 实例化时用户代码可重新扩展该策略类型。

 以上内容摘自Loki库,为做简单示例有简化该单例模式构造模板类。

2. 元编程与其他模式

   Loki template Library库还实现了以下模板元与设计模式的混合

  •  抽象工厂模式
  • 工厂模式
  • 访问者模式

至于具体的讲解大家可以阅读modern C++ programming 一书. 此书就是基于Loki库来编写的。

结尾

1. 读优秀框架源码是为了更好的使用以及修改。

2. 学习优秀的编码思想使得自己不断的成长。

你可能感兴趣的:(QT,源码,c++)