C++语言中的元类编程(三)

来源:互联网 发布:ping网络延迟忽高忽低 编辑:程序博客网 时间:2024/06/03 16:39

现在让我们回到最初的问题,既然我们已经定义了元类,那么我们是不是可以开始着手解决问题了呢?先别着急,我们还需要再了解一个元类编程中非常重要的概念:切入点。

在很多的元类(或面向方面)编程的书和文章中,有关于切入点的非常详细的说明,因此我这里只做简单的介绍。如果你做过windows编程的话,你应该会对windows中的消息机制印象深刻,每一个消息都有一个对应的OnXxx的消息处理函数,你如果需要响应一个消息,就可以实现这个OnXxx函数,如果不需要响应,你就可以简单的忽略它。在这个机制中,每个消息处理函数就是整个消息循环的一个切入点;类似的,在绝大部分的操作系统中都有一套信号机制,它允许程序员(通过信号处理函数)捕获或忽略一个信号,在这个机制中,每个信号处理函数就是整个信号机制的切入点。很显然,一个切入点就是一个回调函数,不难推广,对于元类编程技术来说,我们也可以对我们需要关注的某个函数设置切入点(在这个函数执行前或执行后或两者兼有),那么,要怎么描述一个切入点呢?同样的,我们可以设计一个元切入点类。

根据切入点是回调函数的特性,我们自然会想到它可以是一个元函数类的子类,然而,根据切入点的不同位置(即目标函数调用前后),我们应当注意它的接口是不同的:对于入口切入点(即目标函数调用前的切入点),通常我们用于检查目标函数的参数有效性,或者覆盖目标函数的行为,这样就要求入口切入点需要返回一个值,以指示我们是否应继续执行目标函数;而对于出口切入点(即目标函数调用后的切入点),通常我们用于检查目标函数的返回值或进行错误处理,它需要知道目标函数是否被真正调用到(即入口切入点返回的指示值)和目标函数(或入口切入点)的返回值,以及目标函数抛出的异常(如果启用了C++异常机制)。但是,在我们之前对元函数类的定义中,它的函数入口只有一个输入参数,这就带来了一个问题,我们要如何统一这种函数入口的差异呢?如果我们为每一种切入点定义不同的函数入口,那么就违背了面向对象的设计原则,更重要的,这使得meta_closure不再是一个统一的接口,它的使用将受到限制;如果我们让每种切入点各自实现一个参数结构的转换,比如象下面这样:

struct meta_enter_point: meta_closure {

    struct arg_wrapper_t {

        void * actualArgs;

        bool ignoreTarget;

    };

    void * operator ()(void * args) {

        arg_wrapper_t argWrapper = {false, args};

        return this->closureEntry(&argWrapper);

    }

};

struct meta_leave_point: meta_closure {

    struct arg_wrapper_t {

        bool targetIgnored;

        void * retVal;

        std::exception * error;

    };

    void * operator ()(arg_wrapper_t * argWrapper) {

        return this->closureEntry(argWrapper);

    }

};

这样不但非常麻烦(请考虑每次从meta_closure派生一个新接口或实现类的情况),而且当我们实现它们各自的closureEntry函数时,也需要再做一次参数类型的转换。这些都很容易引入人为的错误。幸运的是,C++为我们提供了一个方法来解决这个问题,那就是使用模板(即编译期元类编程)!为了使用模板,我们需要对meta_closure做一些小的修改:

struct meta_closure {

    virtual const char * name() const = 0;

    virtual size_t argSize() const = 0;

    virtual size_t retSize() const = 0;

    virtual void closureEntry(void * args) = 0; // 请注意我们仅需要修改函数入口的返回值类型,这么做的原因见下面的代码

};

template <typename closure_traits>
struct meta_meta_closure: meta_closure {

    typedef typename closure_traits::arg_type arg_type;

    typedef typename closure_traits::ret_type ret_type;

    struct arg_wrapper {

        arg_type args;

        ret_type retVal;

    };

    virtual size_t argSize() const {

        return sizeof(arg_type);

    }

    virtual size_t retSize() const {

        return sizeof(ret_type);

    }

    virtual void closureEntry(arg_wrapper * argWrapper) = 0;

    virtual void closureEntry(void * args) {

        this->closureEntry( static_cast<arg_wrapper *>(args) );

    }

};

我们可以看到,meta_meta_closure模板对meta_closure做了极大的简化,从meta_meta_closure模板派生出来的类只需要关注closureEntry的具体功能(从而可以减少代码的冗余量,提高可读性和可维护性)。可能有些朋友会问,那为什么不直接使用meta_meta_closure模板定义runtime_class就好了呢?那是因为在C++中,模板实例化的类如果模板参数不同,它就是不同的类,所以它不能作为统一的接口,除非你设计的程序全部使用模板实现。

现在我们就可以定义出相对简单的元切入点类了:

template <typename target_traits>
struct meta_cut_point {

    typedef typename target_traits::arg_type target_arg_type;

    typedef typename target_traits::ret_type target_ret_type;

    struct enter_point_args {

        target_arg_type * targetArgs;

        bool ignoreTarget;

    };

    struct enter_point_traits {

        typedef enter_point_args arg_type;

        typedef target_ret_type ret_type;

    };

    typedef meta_meta_closure<enter_point_traits> meta_enter_point;

    struct leave_point_args {

        const enter_point_args * enterPointArgs;

        target_ret_type * targetRetVal;

        std::exception * error;

    };

    struct leave_point_traits {

        typedef leave_point_args arg_type;

        typedef target_ret_type * ret_type;

    };

    typedef meta_meta_closure<leave_point_traits> meta_leave_point;

}; // 在上面的定义中,我们采用了成员类的形式来定义元切入点类,这样做是为了减少一些代码冗余。

细心一些的朋友可能会想到,为什么不把切入点的接口直接定义在元函数类里呢?首先,这样做有些浪费,因为不是每一个函数都需要我们关注;其次,从效率上来说,查找函数切入点是一个瓶颈点,应该越快越好,然而,我们之后还会看到,采用元类思想后,函数的调用方法会有所不同,而查找函数的入口点也是一个瓶颈点,两个瓶颈点耦合在一起不利于我们对算法进行优化。

因为引入了切入点的概念,现在我们需要修改一下我们的元类设计:

struct runtime_class {

    virtual const char * name() const = 0;

    virtual size_t dataSize() const = 0;

    virtual size_t methodCount() const = 0;

    virtual meta_closure ** methodTable() const = 0;

    virtual meta_closure * regCutPoint(meta_closure * target, bool enterOrLeave, meta_closure * cutPoint) = 0;

    // 注意:我们并不需要一个findCutPoint函数,因为对切入点的调用应当是自动完成的。

};

好!万事具备,只欠东风!

0 0
原创粉丝点击