我有几乎有相同数量的类实现,唯一不同的是它们操作的基础数据类型:
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;
这很有效。 但是我还想为这些专门的类添加一个抽象基类,以便能够以通用的方式操作它们(例如,在集合中)。问题是这个基类应该有getData
和setData
方法,即使不知道操作对象的动态类型也能调用它们。
我会用这样的东西来实现它:
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:如何在几个完全专门化的模板类上实现抽象基类?
答案 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;
}