如何跟踪(枚举)实现接口的所有类

时间:2009-08-11 15:05:29

标签: c++ class enumerate

我有一种情况,我有一个界面,定义某个类的行为,以填补我的程序中的某个角色,但在这个时间点,我不是100%肯定我会写多少个类填补这个角色。但是,与此同时,我知道我希望用户能够从GUI组合/列表框中选择实现他们想要用来填充某个角色的界面的具体类。我希望GUI能够枚举所有可用的类,但是每当我决定实现一个新类来填充该角色时(我可能需要几个月的时间),我宁愿不必返回并更改旧代码。

我考虑过的一些事情:

  1. 使用枚举
    • 优点:
      1. 我知道该怎么做
    • 缺点
      1. 我必须在添加新类时更新枚举
      2. 难以迭代
  2. 在接口中使用某种static列表对象,并在实现类的定义文件中添加新元素
    • 优点:
      1. 无需更改旧代码
    • 缺点:
      1. 甚至不确定这是否可能
      2. 不确定要存储哪种信息,以便工厂方法可以选择正确的构造函数(可能是字符串和返回指向接口对象的指针的函数指针之间的映射)
  3. 我猜这是一个问题(或类似的问题),更有经验的程序员可能在之前(经常)遇到过,并且这种问题可能有一个共同的解决方案,这几乎肯定更好比任何我能想出的东西都要多。那么,我该怎么做呢?

    (P.S。我搜索过,但我找到的只是这个,而且不一样:How do I enumerate all items that implement a generic interface?。看来他已经知道如何解决我想弄清楚的问题了。)

    编辑:我将标题重命名为“我怎样才能跟踪......”,而不仅仅是“我如何枚举...”,因为原来的问题听起来像我对检查运行时环境更感兴趣,其中我真正感兴趣的是编译时簿记。

6 个答案:

答案 0 :(得分:6)

创建一个单例,您可以使用指向创建者函数的指针来注册类。 在具体类的cpp文件中,您注册每个类。
像这样:

class Interface;
typedef boost::function<Interface* ()> Creator;

class InterfaceRegistration
{
    typedef map<string, Creator> CreatorMap;
public:
    InterfaceRegistration& instance() {  
        static InterfaceRegistration interfaceRegistration;
        return interfaceRegistration;
    }

    bool registerInterface( const string& name, Creator creator )
    {
        return (m_interfaces[name] = creator);
    }

    list<string> names() const
    {
        list<string> nameList;  
        transform(
            m_interfaces.begin(), m_interfaces.end(), 
            back_inserter(nameList) 
            select1st<CreatorMap>::value_type>() );
    }

    Interface* create(cosnt string& name ) const 
    { 
        const CreatorMap::const_iterator it 
            = m_interfaces.find(name);  
        if( it!=m_interfaces.end() && (*it) )
        {
            return (*it)();
        }
        // throw exception ...
        return 0;
    }

private:
    CreatorMap m_interfaces;
};


// in your concrete classes cpp files
namespace {
bool registerClassX = InterfaceRegistration::instance("ClassX", boost::lambda::new_ptr<ClassX>() );
}

ClassX::ClassX() : Interface()
{
    //....
}

// in your concrete class Y cpp files
namespace {
bool registerClassY = InterfaceRegistration::instance("ClassY", boost::lambda::new_ptr<ClassY>() );
}

ClassY::ClassY() : Interface()
{
    //....
}

答案 1 :(得分:3)

我依旧记得多年前做过类似的事情。您的选择(2)几乎就是我所做的。在这种情况下,它是std::map std::stringstd::typeinfo。在每个.cpp文件中,我注册了这样的类:

static dummy = registerClass (typeid (MyNewClass));

registerClass获取type_info个对象,只返回true。您必须初始化变量以确保在启动时调用registerClass。简单地在全局命名空间中调用registerClass是一个错误。使dummy静态允许您在没有名称冲突的情况下在编译单元中重用该名称。

答案 2 :(得分:2)

我提到这篇文章实现了一个类似于TimW答案中描述的自注册类工厂,但它有一个很好的技巧,即使用模板化工厂代理类来处理对象注册。值得一看:)

C ++中的自注册对象 - &gt; http://www.ddj.com/184410633

修改

这是我做的测试应用程序(整理了一点;):

object_factory.h

#include <string>
#include <vector>
// Forward declare the base object class
class Object;
// Interface that the factory uses to communicate with the object proxies
class IObjectProxy {
public:
    virtual Object* CreateObject() = 0;
    virtual std::string GetObjectInfo() = 0;
};
// Object factory, retrieves object info from the global proxy objects
class ObjectFactory {
public:
    static ObjectFactory& Instance() {
        static ObjectFactory instance;
        return instance;
    }
    // proxies add themselves to the factory here 
    void AddObject(IObjectProxy* object) {
        objects_.push_back(object);
    }
    size_t NumberOfObjects() {
        return objects_.size();
    }
    Object* CreateObject(size_t index) {
        return objects_[index]->CreateObject();
    }
    std::string GetObjectInfo(size_t index) {
        return objects_[index]->GetObjectInfo();
    }

private:
    std::vector<IObjectProxy*> objects_;
};

// This is the factory proxy template class
template<typename T>
class ObjectProxy : public IObjectProxy {
public:
    ObjectProxy() {
        ObjectFactory::Instance().AddObject(this);
    }        
    Object* CreateObject() {
        return new T;
    }
    virtual std::string GetObjectInfo() {
        return T::TalkToMe();
    };    
};

objects.h

#include <iostream>
#include "object_factory.h"
// Base object class
class Object {
public:
    virtual ~Object() {}
};
class ClassA : public Object {
public:
    ClassA() { std::cout << "ClassA Constructor" << std::endl; }
    ~ClassA() { std::cout << "ClassA Destructor" << std::endl; }
    static std::string TalkToMe() { return "This is ClassA"; }
};
class ClassB : public Object {
public:
    ClassB() { std::cout << "ClassB Constructor" << std::endl; }
    ~ClassB() { std::cout << "ClassB Destructor" << std::endl; }
    static std::string TalkToMe() { return "This is ClassB"; }
};

objects.cpp

#include "objects.h"
// Objects get registered here
ObjectProxy<ClassA> gClassAProxy;
ObjectProxy<ClassB> gClassBProxy;

的main.cpp

#include "objects.h"
int main (int argc, char * const argv[]) {
    ObjectFactory& factory = ObjectFactory::Instance();
    for (int i = 0; i < factory.NumberOfObjects(); ++i) {
        std::cout << factory.GetObjectInfo(i) << std::endl;
        Object* object = factory.CreateObject(i);
        delete object;
    }
    return 0;
}

输出:

This is ClassA
ClassA Constructor
ClassA Destructor
This is ClassB
ClassB Constructor
ClassB Destructor

答案 3 :(得分:1)

如果您使用的是Windows,并且使用的是C ++ / CLI,则此操作变得非常简单。 .NET框架通过反射提供此功能,并且在托管代码中非常干净地工作。

在本机C ++中,这有点棘手,因为没有简单的方法来查询库或应用程序的运行时信息。有许多frameworks that provide this(只是寻找IoC,DI或插件框架),但最简单的方法是自己做一些配置,工厂方法可以使用它来注册自己,并返回一个实现您的特定基类。你只需要实现加载DLL,并注册工厂方法 - 一旦你拥有它,它就相当容易。

答案 4 :(得分:1)

你可以考虑的是一个对象计数器。这样您就不需要更改分配的每个位置,只需更改实现定义。它是工厂解决方案的替代品。考虑利弊。

这样做的一个优雅方法是使用CRTP : Curiously recurring template pattern。 主要的例子就是这样一个计数器:)

这样你只需添加具体的类实现:

class X; // your interface

class MyConcreteX : public counter<X>
{
    // whatever
};

当然,如果您使用未掌握的外部实现,则不适用。

编辑:

要处理确切的问题,您需要一个只计算第一个实例的计数器。

我的2美分

答案 5 :(得分:0)

无法在(本机)C ++中查询类的子类。 你如何创建实例?考虑使用工厂方法,允许您迭代您正在使用的所有子类。当您创建这样的实例时,将来不可能忘记添加新的子类。