65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET 风格的委托(Delegate)在非托管 C++ 中的实现

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.72/5 (13投票s)

2002 年 9 月 21 日

4分钟阅读

viewsIcon

133472

downloadIcon

470

本文介绍了一种设计模式,如何在普通/非托管 C++ 中实现 .NET 中所见的通用委托。

引言

C#、VB.NET 和 MC++ 的用户有一个很方便的功能:委托。C++ 语言不支持这个构造。但幸运的是,可以使用模板和从 boost 库借用的一些巧妙技巧来实现基本的委托。

我假设您有扎实的 C++ 背景。在本文中,我将使用成员函数指针和模板来解决委托问题。在继续阅读之前,您可能想先回顾一下这些主题。

什么是委托?

对于那些尚未接触过 .NET 语言的读者,这里有一个简短的解释。

简单来说,委托是允许在对象上调用方法的对象。有什么大不了的?这很重要,因为这些对象伪装成与特定对象没有任何耦合关系的自由函数。顾名思义,它将方法调用委托给目标对象。

C++ 对委托的第一次尝试

由于可以获取成员函数的地址,并在定义该成员函数的类的任何对象上应用该成员函数,因此理应可以创建一个委托构造。一种方法是存储对象的地址以及其成员函数之一。存储可以是一个重载了 operator() 的对象。operator() 的类型签名(返回类型和参数类型)应与我们要用于委托的成员函数的类型签名匹配。一个非常非动态的版本可以是

struct delegate {
    type*   obj;                    
        // The object which we delegate the call to

    int (type::* method)(int);      
        // Method belonging to type, taking an int 
        // and returning an int 

    delegate(type* obj_, 
        int (type::* method)(int)) : obj(obj_), 
        method(method_) { }

    int operator()(int x) {         
            // See how this operator() matches 
            // the method type signature

        return (obj->*method)(x);   
            // Make the call
    }
};

上述解决方案在任何方面都不是动态的。它只能处理类型为 type 的对象、接受 int 并返回 int 的方法。这迫使我们为希望委托的每种对象类型/方法组合编写新的委托类型,或者使用对象多态性,其中所有类都从 type 派生——并且只能委托在 type 中定义的、与 int/int 类型签名匹配的虚方法!显然,这不是一个好的解决方案。

C++ 对委托的第二次尝试

显然,我们需要参数化对象类型、参数类型和返回类型。在 C++ 中做到这一点的唯一方法是使用模板。第二次尝试可能看起来像这样

template <typename Class, typename T1, typename Result>
struct delegate {
    typedef Result (Class::* MethodType)(T1);
    Class*          obj;
    MethodType      method;

    delegate(Class* obj_, 
        MethodType method_) : obj(obj_), method(method_) { }

    Result operator()(T1 v1) {
        return (obj->*method)(v1);
    }
};

好多了!现在我们可以委托任何对象和该对象中的任何单参数方法。这比之前的实现有了明显的改进。

不幸的是,无法编写能够处理任意数量参数的委托。要解决具有两个参数的方法的问题,必须编写一个新的处理两个参数的委托。要解决具有三个参数的方法的问题,必须编写一个新的处理三个参数的委托——以此类推。但这也不是一个大问题。如果您需要覆盖所有方法,很可能不需要超过十个这样的委托模板。您有多少方法有超过十个参数?如果有,您确定它们应该有超过十个参数吗?另外,您只需要编写这十个委托一次——这是模板的强大之处。

然而,除了参数问题之外,还有一个小问题仍然存在。当实例化此委托模板时,生成的委托类型将只能处理您作为模板参数提供的类的委托。delegate<A, int, int> 类型与 delegate<B, int, int> 不同。它们相似之处在于它们都委托接受 int 并返回 int 的方法调用。它们不同之处在于它们不委托同一类的成员函数。.NET 委托忽略这种差异,我们也应该如此!

C++ 对委托的第三次也是最后一次尝试

为了消除这种类型差异,很明显我们需要移除类类型作为模板参数。这最好通过使用对象多态性和模板构造函数来实现。这是一项我从 boost 模板库中借用的技术。具体来说,我从该库中 any 类的实现中借用了它。

由于我不是英语母语者,我不会尝试用文字来描述最终的代码。我可以尝试,但我认为我只会让它比实际更复杂。简单来说,我使用多态性和模板构造函数来获得一个额外的间接层,这样我就可以从委托中“剥离”类信息。代码如下

// The polymorphic base
template <typename T1, typename Result>
struct delegate_base { // Ref counting added 2002-09-22
    int ref_count; // delegate_base's are refcounted

    delegate_base() : ref_count(0) { }

    void addref() {
        ++ref_count;
    }

    void release() {
        if(--ref_count < 0)
            delete this;
    }

    virtual ~delegate_base() { } // Added 2002-09-22
    virtual Result operator()(T1 v1) = 0;
};

// The actual implementation of the delegate
template <typename Class, typename T1, typename Result>
struct delegate_impl : public delegate_base<T1, Result> {
    typedef Result (Class::* MethodType)(T1);
    Class*          obj;
    MethodType      method;

    delegate_impl(Class* obj_, MethodType method_) : 
            obj(obj_), method(method_) { }

    Result operator()(T1 v1) {
        return (obj->*method)(v1);
    }
};

template <typename T1, typename Result>
struct delegate {
    // Notice the type: delegate_base<T1, 
    // Result> - no Class in sight!
    delegate_base<T1, Result>*        pDelegateImpl;

    // The templated constructor - The presence of Class 
    // does not "pollute" the class itself
    template <typename Class, typename T1, typename Result>
    delegate(Class* obj, Result (Class::* method)(T1)) 
        : pDelegateImpl(new delegate_impl<Class, 
        T1, Result>(obj, method)) { 
            pDelegateImpl->addref(); // Added 2002-09-22
        }

        // Copy constructor and assignment operator 
        // added 2002-09-27
        delegate(const delegate<T1, 
            Result>& other) {
            pDelegateImpl = other.pDelegateImpl;
            pDelegateImpl->addref();
        }

        delegate<T1, Result>& operator=(const delegate<T1, 
            Result>& other) {
            pDelegateImpl->release();
            pDelegateImpl = other.pDelegateImpl;
            pDelegateImpl->addref();
            return *this;
        }

        ~delegate() { pDelegateImpl->release(); 
            } // Added & modified 2002-09-22

        // Forward the delegate to the delegate implementation
        Result operator()(T1 v1) {
            return (*pDelegateImpl)(v1);
        }
};

这样,.NET 委托的要求就满足了!有关如何实际使用委托的信息,请参阅本文顶部提供的演示源代码。

为什么?

因为我认为委托可能非常强大,而且我个人喜欢工具箱里有强大的工具。它们可能有一天会有用!

更新

  • 2002-09-22:移除了内存泄漏
  • 2002-09-22:修复内存泄漏引入了一个更严重的问题:悬空指针。使用 COM 式的引用计数来解决。
  • 2002-09-27:添加了复制构造函数和赋值运算符
© . All rights reserved.