C++类扩展

问题描述:

有没有像C#的类扩展方法修改原始类定义(即编译.lib包含类和相应的.h文件)的方法来添加新方法的类?C++类扩展

+1

为了将来的参考,这被称为'猴子修补'。 – LiraNuna 2009-10-25 20:16:04

编号C++没有这样的能力。

正如在其他的答案中提到,常见的解决方法是:

  • 定义一个派生类,或许还有一个工厂隐藏实际的实现类
  • 定义decorator
  • 定义一个操作功能在类的实例
+0

也可以通过[double dispatch](http://en.wikipedia.org/wiki/Double_dispatch) – Bossliaw 2012-04-07 12:55:35

不,你不能在C++中做到这一点。

如果你想实现这样的事情,你有2种选择,

  • 你可以从你的类继承的(如果这是一种选择,因为该类可能没有被写入到它可能不是法律允许继承)
  • 你可以编写自己的包装类,它具有相同的接口+你的新方法,并委托给你想要扩展的接口。

我更喜欢委托方式。

+4

+1来支持“支持构图而不是继承” – Rob 2009-10-25 17:43:32

+0

好的回复 - 但我不确定有人提问当他听到像“继承”或“委托”这样的术语时,他们知道要实施什么。 – 2009-10-25 20:24:16

+1

@mh,可能不是。但是现在他们知道将正确的术语放入SO或Google搜索中 – Glen 2009-10-25 20:37:31

对不起,没有。一旦你的代码在obj中,你就不能改变它。如果这可以在VC中完成,则部分类将已经被支持。但有一个例外,运算符方法可以使用全局函数进行扩展,就像在STL中如何实现cout <。

C#类的扩展方法大多是语法糖。您可以使用*函数获得相同的功能(即,具有对您的类的引用或常量引用作为其第一个参数的函数)。既然这对STL很有效,那么为什么不能为你的班级?当然

,您可以:


template <typename Ext> 
class Class: public Ext { /* ... */ }; 

这并不意味着它虽然最好的办法。

一般不会。但是,如果库不会创建需要扩展名的类的实例,并且您可以修改应用中创建类的实例并需要扩展名的所有位置,则可以使用以下方法:

  • 创建一个工厂函数,该函数在所有需要该类实例的地方调用,并返回一个指向该实例的指针(google的Design Patterns Factory,...)。
  • 用你想要的扩展名创建派生类。
  • 使工厂函数返回派生类而不是原始类。

例子:


    class derivedClass: public originalClass { /* ... */}; 

    originalClass* createOriginalClassInstance() 
    { 
     return new derivedClass(); 
    } 
  • 每当你需要访问的扩展,需要到原班人马强制转换为派生类,当然。

这大致是如何实现Glen提出的“继承”方法。格伦的“包装类具有相同的界面”方法从理论角度来看也非常好,但具有略微不同的属性,使得它不太可能适用于您的案例。

有一种方法可以完成。这就是放松你的需求。在C++中,人们经常说,类的接口不仅包含其成员函数,还包含类中的所有函数。

也就是说,可以将该类作为参数给予的非成员函数应该被视为其接口的一部分。

例如,std::find()std::sort()std::vector的接口的一部分,即使它们不是该类的成员。

如果你接受这个定义,那么你总是可以通过添加非成员函数来扩展一个类。

您不能将方法或数据物理地添加到二进制形式的类文件中。但是,您可以通过编写扩展类来为该类的对象添加方法和数据(功能和状态)。这不是直接的,需要基于元对象协议和基于接口的编程。你需要做很多事情才能在C++中实现这一点,因为它不支持开箱即用。在这样的实现中,当您通过原始类对象指针查询由新扩展类实现的接口时,元对象实现将通过它在运行时创建的扩展类的元类对象返回该接口指针。 这是多少可定制的(基于插件的)软件应用程序框架的工作原理。但是,您必须记住,它需要将许多其他MOP机制写入所有使用描述对象关系的字典的实例元对象,并为原始对象和扩展类对象提供正确的接口指针。 Dassault Systemes的CATIA V5采用这种名为CAA V5的体系结构编写,您可以通过编写具有所需功能的新扩展类来扩展现有组件。

在C++中,您可以使用*函数,但有时扩展方法在将许多函数嵌套在一起时效果更好。看看这个C#代码:

var r = numbers.Where(x => x > 2).Select(x => x * x); 

如果我们写这在C++使用免费的功能,它应该是这样的:

auto r = select(where(numbers, [](int x) { return x > 2; }), [](int x) { return x * x; }); 

这不仅是难以阅读,但很难来写。解决这个问题的常用方法是创建所谓的可调用函数。这些函数是通过重载|管道运算符(这只是真正的运算符)而创建的。所以上面的代码可以这样写:

auto r = numbers | where([](int x) { return x > 2; }) | select([](int x) { return x * x; }); 

这是更容易阅读和写入。许多库对范围使用pipable函数,但它也可以扩展到其他类。 Boost在它们的range库中使用它,pstade oven使用它,并且此C++ linq库也使用它。

如果你想编写自己的pipable函数,请解释如何做到这一点here。但是,其他库提供函数适配器以使其更容易。 Pstade鸡蛋有一个pipable adaptor,linq提供了range_extension适配器来创建一个至少为范围的可调函数。

使用LINQ,首先您只需建立功能函数对象是这样的:

struct contains_t 
{ 
    template<class Range, class T> 
    bool operator()(Range && r, T && x) const 
    { return (r | linq::find(x)) != boost::end(r); }; 
}; 

然后你使用静态初始化这样的初始化函数:

range_extension<contains_t> contains = {}; 

然后你可以使用你的pipble功能是这样的:

if (numbers | contains(5)) printf("We have a 5");