A common base class with several specialized derived template classes

I have a finite number of classes with almost the same implementation, the only thing that is the main data type they manage:

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. 

I would like to reduce code duplication of these classes using such patterns:

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

And specialization:

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

It works well. But I would also like to add an abstract base class to these specialized classes in order to be able to manipulate them in a general way (for example, in a collection). The problem is that this base class must have getData and setData methods in order to be able to call them without even knowing the dynamic type of the object being processed.

I would execute it with something like this:

 class Base { public: virtual void setData(??? data) = 0; virtual ??? getData() = 0; }; // Modify GenericContainer definition like so template<typename T> class GenericContainer : Base { ... } 

And use it something like this:

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

The problem is that I donโ€™t know how to write prototypes of Base methods, since the type is unknown (and it doesnโ€™t matter, the implementation of the derived class should be called at run time based on the dynamic type of the object).

TL DR: How to implement an abstract base class over several fully specialized templates?

+7
source share
4 answers

It is simply impossible to do what you want.

The problem is that if this was allowed, the compiler would have to generate as many virtual methods in the base class as possible specializations of the template child class (i.e. infinity), which is impossible.

+4
source

How to make a base template too? Of course you cannot do something like

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

but otherwise you can achieve something simple:

 template<typename T> class Base { public: virtual void setData(T data) = 0; virtual T getData() = 0; }; // Modify GenericContainer 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; } }; 

You can use it in any way as long as the types match.

 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
source

This is a solution for any type of class that can move through a stringstream , and such a conversion is the right way to convert between types. This is inefficient:

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

with fewer types, you can do something similar if you have conversion functions between them.

Alternatively, if you like exceptions, you can quit.

Alternatively, you can use boost::variant s, which do not convert but work from a finite list of types (they are mostly labeled union , which support more types than C ++ 03, allows union to do, and with some good semantics on assign / copy / etc).

+1
source

Assuming you have design flexibility, you can change your interface to fit this, although it is not as efficient as an infinite virtual table

You can set values โ€‹โ€‹through the construction or >>

You can get the values โ€‹โ€‹in <<

Your vector must be a base pointer or link, the size of each base object is variable, the pointer, explicit or implicit through the link, has a fixed size

Note that copies are more efficient if the compiler knows that it copies from one pedigree to another, and not the database to the database

 #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; } 
+1
source

All Articles