8

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

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

4 回答 4

5

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

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

于 2013-04-26T16:20:01.100 回答
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.
于 2013-04-26T16:54:59.980 回答
1

这是任何可以通过 a 往返的类的解决方案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,并且在分配/复制/等方面具有一些很好的语义) .

于 2013-04-26T18:40:22.050 回答
1

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

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

您可以通过<<

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;
}
于 2015-09-03T17:27:14.507 回答