【问题标题】:Generic base class with multiple template specialized derived classes具有多个模板专用派生类的通用基类
【发布时间】:2013-04-20 21:14:43
【问题描述】:

我有有限数量的类,它们的实现几乎相同,唯一不同的是它们操作的底层数据类型:

class IntContainer
{
public:
    void setData(int data);
    int getData();
    int _data;
};

class BoolContainer
{
public:
    void setData(bool data);
    bool getData();
    bool _data;
};

class StringContainer
{
public:
    void setData(std::string data);
    std::string getData();
    std::string _data;
};

// Etc. You get the idea.

我想通过使用这样的模板来减少这些类的代码重复:

template<typename T>
class GenericContainer
{
public:
    void setData(T data);
    T getData();
    T _data;
};

和专业化:

typedef GenericContainer<int> IntContainer;
typedef GenericContainer<bool> BoolContainer;
typedef GenericContainer<std::string> StringContainer;

这很好用。 但是我还想为这些专门的类添加一个抽象基类,以便能够以通用方式(例如在集合中)操作它们。问题是这个基类应该有getDatasetData 方法,即使不知道被操作对象的动态类型也能调用它们。

我会用这样的方式来实现它:

class Base
{
public:
    virtual void setData(??? data) = 0;
    virtual ??? getData() = 0;
};

// Modify GenericContainer's definition like so
template<typename T>
class GenericContainer : Base { ... }

然后以某种方式使用它:

int main(int argc, char const *argv[])
{
    IntContainer intc = IntContainer();
    intc.setData(42);
    std::cout << intc.getData() << std::endl;

    BoolContainer boolc = BoolContainer();
    boolc.setData(false);
    std::cout << boolc.getData() << std::endl;

    std::vector<Base> v;
    v.push_back(intf);
    v.push_back(boolf);

    for (std::vector<Base>::iterator it = v.begin() ; it != v.end(); ++it)
        std::cout << it->getData() << std::endl;

    return 0;
}

问题是我不知道如何编写Base 方法原型,因为类型是未知的(没关系,应该在运行时根据对象的动态类型调用派生类实现)。

TL;DR:如何在几个完全专用的模板类上实现抽象基类?

【问题讨论】:

  • 你希望通过添加这个基类来做什么?对此的了解将有力地塑造潜在的答案。
  • 基本上,我希望有一组强类型派生类方法(即不能在BoolContainer 上调用setData(int data)),但能够迭代一组这些类(可能是std::vector&lt;Base*&gt;)。
  • ...但也许我这些天在用 Objective-C 思维思考太多了。
  • 当您想将此行为延迟到运行时(这些对象的任意集合)时,我认为错误的选择是选择编译时多态性(模板)

标签: c++ templates inheritance abstract-class


【解决方案1】:

根本没有办法做你想做的事。

问题是,如果允许这样做,编译器必须在基类中生成尽可能多的虚拟方法,因为模板子类的可能特化(即无穷大)是不可能的。

【讨论】:

  • 同意——你想要的是一种动态类型的语言,而 C++ 不是。
  • 知道我只需要有限数量的派生类(我不想要“完全通用”,只需要 DRY 代码),您知道解决我的问题的方法吗?
【解决方案2】:

制作基本模板怎么样?当然,你不可能做类似的事情

std::vector<Base> v;
v.push_back(intf);
v.push_back(boolf);

但其余的你可以用一些简单的东西来实现

template<typename T>
class Base
{
public:
    virtual void setData(T data) = 0;
    virtual T getData() = 0;
};

// Modify GenericContainer's definition like so
template<typename T>
class GenericContainer : Base<T> { 
    T d;
    public:
    virtual void setData(T data) {d = data;}
    virtual T getData() { return d; }
};

只要类型匹配,您就可以以任何方式使用它。

IntContainer intc = IntContainer();
intc.setData(42);
std::cout << intc.getData() << std::endl;

BoolContainer boolc = BoolContainer();
boolc.setData(true);
std::cout << boolc.getData() << std::endl;

std::vector<IntContainer> v;
v.push_back(intc);
// v.push_back(boolc); No can't do.

【讨论】:

    【解决方案3】:

    这是任何可以通过stringstream 往返的类的解决方案,并且这种转换是类型之间转换的正确方法。它根本没有效率:

    struct BaseContainer {
    protected:
      boost::any data;
      std::function< std::string( boost::any const& ) > toString;
      virtual void setDataAny( boost::any x, std::function< std::string( boost::any const& ) > convert ) {
        data = x;
        toString = convert;
      }
    public:
      virtual boost::any getDataAny() const {
        return data;
      }
      template<typename T>
      void setData( T const& t ) {
        setDataAny( boost::any(t), []( boost::any const& a )->std::string {
          std::string retval;
          std::stringstream ss;
          try
          {
            ss << boost::any_cast< T >(a);
            ss >> retval;
            return retval;
          } catch(const boost::bad_any_cast &) {
            return retval;
          }
        });
      };
    template<typename T>
    struct TypedContainer:BaseContainer {
    public:
      T getData() const {
        T retval;
        try {
          retval = boost::any_cast<T>(getDataAny());
          return retval;
        } catch(const boost::bad_any_cast &) {
          std::string str = toString( getDataAny() );
          std::stringstream ss;
          ss << str;
          ss >> retval;
          return retval;
        }
      }
    };
    

    使用更少的类型,你可以做类似的事情,只要你有它们之间的转换函数。

    或者,如果你喜欢异常,你可以抛出。

    或者,您可以使用boost::variants,它不进行任何转换,但可以从有限的类型列表中工作(它们基本上被标记为unions,它支持的类型比C++03 允许union 做的更多,并在分配/复制/等方面有一些很好的语义)。

    【讨论】:

      【解决方案4】:

      假设您具有一定的设计灵活性,您可以更改您的界面以适应这一点,尽管它不如无限虚拟表高效

      你可以通过构造来设置值,或者&gt;&gt;

      您可以通过&lt;&lt;获取值

      您的vector 需要是基指针或引用,每个基对象的大小是可变的,通过引用显式或隐式的指针是固定的size

      请注意,如果编译器知道它是从一个泛型复制到另一个泛型而不是从基到基复制,那么复制效率会更高

      #include <iostream>
      #include <sstream>
      #include <vector>
      
      class gen_base
      {
      public:
          virtual std::ostream & output(std::ostream& S) const = 0;
          virtual std::istream & input(std::istream& S) = 0;
      
          friend std::istream & operator >> (std::istream &S, gen_base &g) {
              return g.input(S);
          }
      
          friend std::ostream & operator << (std::ostream &S, const gen_base &g) {
              return g.output(S);
          }
      };
      
      template<typename T>
      class GenericContainer : public gen_base
      {
      public:
          GenericContainer(T data) : _data(data) {}
          GenericContainer(const gen_base& other) {
      // std::cout << "EXPENSIVE" << std::endl;
              std::stringstream cvt;
              other.output(cvt);
              input(cvt);
          }
          template <class U>
          GenericContainer(const GenericContainer<U>& other)
          {
      // std::cout << "CHEAP" << std::endl;
              _data=other.getData();
          }
          virtual std::istream & input(std::istream &S) {
              return (S >> _data);
          }
          virtual std::ostream & output(std::ostream &S) const {
              return (S << _data);
          }
          T getData() const {
            return _data;
          }
      private:
          T _data;
      };
      
      typedef GenericContainer<int> IntContainer;
      typedef GenericContainer<bool> BoolContainer;
      typedef GenericContainer<std::string> StringContainer;
      
      int main(int argc, char const *argv[])
      {
          IntContainer * intc = new IntContainer(42);
          std::cout << *intc << std::endl;
      
          gen_base * boolc = new BoolContainer(*intc);
          std::cout << *boolc << std::endl;
      
          IntContainer * intc2 = new IntContainer(*boolc);
          std::cout << *intc2 << std::endl;
      
          std::vector<gen_base *> v; // has to be pointer to base;
          v.push_back(intc);
          v.push_back(boolc);
          v.push_back(intc2);
      
          for (std::vector<gen_base *>::iterator it = v.begin() ; it != v.end(); ++it)
              std::cout << **it << std::endl;
      
          delete intc;
          delete boolc;
      
          return 0;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-02-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-09-23
        • 1970-01-01
        相关资源
        最近更新 更多