【问题标题】:Creating dynamic type in C++在 C++ 中创建动态类型
【发布时间】:2013-04-09 11:29:24
【问题描述】:

我正在编写一个通用软件,它将加载到相同基本硬件的许多不同变体上。它们都具有相同的处理器,但具有不同的外围设备和它们自己需要执行的功能。软件将通过读取硬件开关值来知道它应该运行哪个变体。

简而言之,这是我当前的实现:

class MyBase
{
public:
    MyBase() { }
    virtual run() = 0;
}


class VariantA : public MyBase
{
public:
    VariantA () { }
    virtual run()
    {
        // Run code specific to hardware Variant-A
    }
}


class VariantB : public MyBase
{
public:
    VariantB () { }
    virtual run()
    {
        // Run code specific to hardware Variant-B
    }
}


void main()
{
    MyBase* variant;
    uint_8 switchValue = readSwitchValue();

    switch(switchValue)
    {
    case 0:
        variant = new VariantA();
        break;

    case 1:
        variant = new VariantB();
        break;
    }

    variant->run();
}

现在这工作得很好。我读取了硬件值并使用 switch 语句创建了新的相应类。

问题是我必须处理很多变体。目前大约有 15 个,有可能在不久的将来再增加 20-30 个。我真的开始鄙视运行数百行的 switch 语句,所以我真的在寻找更好的方法来做到这一点,可能是通过模板。

我希望能够使用我的硬件值来查找类型并使用该类型来创建我的新对象。理想情况下,当我添加一个新变体时,我会创建新类,将该类类型添加到我的查找表中,并使用它匹配的硬件值,这样就可以了。

这可能吗?这里有什么好的解决方案?

【问题讨论】:

  • 就个人而言,我认为创建适当类的“switch/case”块可能是最佳解决方案。只需将您的 case 语句放在返回对特定类的引用的静态“工厂”方法中。恕我直言......这是一个很好的例子:stackoverflow.com/questions/7468104/…
  • 硬件只能在运行时知道吗?
  • 看看这个特定的answer,它描述了一种通过注册构造函数来构建对象工厂的方法。可能值得查找帖子中提到的原始想法。
  • 另一个有趣的(和相关的)概念:依赖注入。
  • @Kerrek,是的,硬件只能在运行时知道。

标签: c++ templates design-patterns inheritance


【解决方案1】:

如上所述,您创建了一个工厂,但不一定使用幼稚的 switch 语句。您可以做一个模板类来创建相关对象并将它们动态添加到您的工厂。

class VariantinatorBase {
  public:
    VariantinatorBase() {}
    virtual ~VariantinatorBase() {}
    virtual std::unique_ptr<Variant> Create() = 0;
};

template< class T >
class Variantinator : public VariantinatorBase {
  public:
    Variantinator() {}
    virtual ~Variantinator() {}
    virtual std::unique_ptr<Variant> Create() { return std::make_unique<T>(); }
};

现在你有了一个允许你注册这些的类工厂。

class VariantFactory
{
  public:
    VariantFactory()
    {
         // If you want, you can do all your Register() calls in here, and even
         // make the Register() function private.
    }

    template< uint8_t type, typename T >
    void Register()
    {
        Register( type, std::make_unique<Variantinator<T>>() );
    }

    std::unique_ptr<Variant> Create( uint8_t type )
    {
        TSwitchToVariant::iterator it = m_switchToVariant.find( type );
        if( it == m_switchToVariant.end() ) return nullptr;
        return it->second->Create();
    }

  private:
    void Register( uint8_t type, std::unique_ptr<VariantinatorBase>&& creator )
    {
        m_switchToVariant[type] = std::move(creator);
    }

    typedef std::map<uint8_t, std::unique_ptr<VariantinatorBase> > TSwitchToVariant;
    TSwitchToVariant m_switchToVariant;
};

在您的程序开始时,创建工厂并注册您的类型:

VariantFactory factory;
factory.Register<0, VariantA>();
factory.Register<1, VariantB>();
factory.Register<2, VariantC>();

然后,你想调用它:

std::unique_ptr<Variant> thing = factory.Create( switchValue );

【讨论】:

  • +1 我喜欢这个设计。一些改进的想法: 1. 添加一个虚拟析构函数到VariantinatorBase。 2.(使用 C++11 时)使 Create 方法返回 std::unique_ptr&lt;Variant&gt; 而不是原始指针。 3.(使用 C++11 时)同样,将 std::unique_ptr&lt;VariantinatorBase&gt; 传递给 VariantFactory::Register
  • @paddy 如果需要,您仍然可以选择将返回的unique_ptr 转换为shared_ptr。返回 unique_ptr 清楚地表明指针的所有权已转移给调用者并防止内存泄漏。
  • 如果允许模板Register 函数了解Variantator,当然可以将代码重复减少到Register&lt;VariantA&gt;(0);
  • @paddy 这并不像用unique_ptrs 替换所有原始指针那么简单。没有从原始指针到unique_ptr 的隐式转换,并且unique_ptrs 不能相互分配(您必须将moveswap 分配给它们)。它应该看起来像这样:pastie.org/7628666
  • 哈,5年后我终于在把它链接到某人后回到这个,并进行了所需的更改。
【解决方案2】:

您正在寻找工厂

http://www.oodesign.com/factory-pattern.html

工厂是一个软件模块(一种方法、一个类),其唯一目的是为工作创建正确的对象。使用工厂类的示例:

class VariantFactory
{
    MyBase* CreateObject(uint_8 value);
}

并且可以填写 CreateObject 方法,为您提供所需的对象类型。

如果选择的对象很少且构造简单,一个简单的 switch 语句可能就足够了。一旦你得到很多对象或需要更详细构造的对象,工厂就非常有用。

【讨论】:

  • 还有更多内容,因为您几乎只是将 switch 语句从 main 移动到 VariantFactory::CreateObject
  • Ben,是的 - 这就是为什么我链接到模式本身,并添加了一些进一步的 cmets。
  • 我看不出这有什么更好的,因为工厂内部仍然会有大量的 switch 语句。我刚刚遇到了这个线程,它有一个类似的问题和有趣的答案。 stackoverflow.com/questions/11831284/…
【解决方案3】:

我对此发表了评论;让我们把它变成一个答案:

就个人而言,我认为创建适当类的“switch/case”块可能是最佳解决方案。只需将您的案例语句放在返回对特定类的引用的静态“工厂”方法中。恕我直言...

这是一个很好的例子:factory method design pattern

Class Book : public Product
{
};

class Computer : public Product
{
};

class ProductFactory
{
public:
  virtual Product* Make(int type)
  {
    switch (type)
    {
      case 0:
        return new Book();
      case 1:
        return new Computer();
        [...]
    }
  }
}

Call it like this:

ProductFactory factory = ....;
Product* p1 = factory.Make(0); // p1 is a Book*
Product* p2 = factory.Make(1); // p2 is a Computer*
// remember to delete p1 and p2

请注意,在他最出色的回应中,smink 还提出了一些其他设计替代方案。

底线:switch/case 块本身并没有什么“错误”。即使对于具有许多案例选项的开关。

恕我直言...

PS: 这真的不是在创建“动态类型”。相反,它是“动态创建静态类型”。如果您也使用模板或枚举解决方案,这同样适用。但同样 - 我非常喜欢“开关/外壳”。

【讨论】:

  • 这(或switch)没有任何问题,但我个人觉得这很乏味且容易出错。
  • 同意。代码必须决定创建什么样的对象,最简单的方法是使用 if-else 链或 switch 语句。当然,您可以创建一个注册表隐藏查找代码中的决定,但这最终会掩盖正在发生的事情。
【解决方案4】:

更新:我将把我原来的解决方案留给后代,但考虑到 paddy 提供的解决方案更优越且不易出错。我认为它实际上只做了一些细微的改进。


考虑这种设计:

class VariantA : public MyBase
{
    static MyBase *CreateMachineInstance() { return new VariantA; }
};

class VariantB : public MyBase
{
    static MyBase *CreateMachineInstance() { return new VariantB; }
};

现在,您只需要一个std::map,它使用uint_8 作为键并将其映射到函数指针(返回MyBase)。在地图中插入标识符(将每个标识符指向适当的机器创建函数),然后阅读代码并使用地图查找您正在使用的机器。

这大致基于称为“工厂”的概念/模式,但如果您的机器构造函数需要不同的参数或者您需要执行额外的每台机器初始化/操作,则可能会稍微中断 - 从您提到的内容来看,这听起来像您可能。

如果是这种情况,您仍然可以使用此模式,但您必须进行一些调整和重新架构一些东西,但您最终会得到一些更清洁、更易于扩充和维护的东西。

【讨论】:

    【解决方案5】:
    #include <stdio.h>
    #include <string.h>
    #include <iostream>
    using namespace std;
    
    template<class T,class T1>
    class HeroHonda
    {
    private:
        T millage;
        T1 *options;
    
    public:
        HeroHonda() {
            puts("constructed");
            options=new T1[20];
    
            strcpy(options,"Good millage,Powerstart");
            millage=110;
        }
    
        virtual T features() {
            cout<<options<<"millage is"<<millage<<endl;
            return 1;
        }
    
        // virtual T Extrafeatures() = 0;
    
        ~HeroHonda() {
          cout<<"destructor"<<endl;
          delete [] options;    
        }
    };
    
    int main()
    {
        HeroHonda <int,char> *Ptr=new HeroHonda <int,char>;
        Ptr->features();
        delete Ptr;
    }
    

    【讨论】:

      猜你喜欢
      • 2017-02-23
      • 2011-06-09
      • 2023-03-14
      • 1970-01-01
      • 2011-12-21
      • 2011-02-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多