具有多个模板专用派生类的通用基类

时间:2013-04-26 16:11:25

标签: c++ templates inheritance abstract-class

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

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:如何在几个完全专门化的模板类上实现抽象基类?

4 个答案:

答案 0 :(得分:5)

根本无法做你想做的事。

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

答案 1 :(得分:3)

如何制作基本模板呢?当然,你无法做像

这样的事情
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.

答案 2 :(得分:1)

这是可以在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::variant s,它们不进行转换,但可以使用有限的类型列表(它们基本上标记为union,支持的类型多于C ++ 03允许{ {1}}做,并且在assign / copy / etc上有一些很好的语义。)

答案 3 :(得分:1)

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

您可以通过构造设置值,或>>

您可以通过<<

获取值

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

请注意,如果编译器知道它正在从一个泛型复制到另一个泛型而不是从base到base,那么副本会更有效

#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;
}