Markus Dehmann wrote:
I have a two different value types with which I want to do similar
things: store them in the same vector, stack, etc. Also, I want an <<
operator for each of them.
OK, templates and the whole STL is designed to support this sort of thing.
class Value{}; // this would be "public interface Value{}" in Java!
As Victor said, not really. In C++, you rarely need empty interfaces.
If your reason for creating a common base class is to express a set of
operations common to different sub-classes, why is the base empty?
class IntValue : public Value{
private:
int _value;
public:
IntValue(int value):_value(value){}
};
#include <string>
class StringValue : public Value{ // constructor etc omitted
private:
std::string _value;
public:
StringValue(std::string value):_value(value){}
};
But how do I realize the << operator? I could do sth like this, then:
#include <iostream>
#include <vector>
int main(){
using namespace std;
vector <Value> val;
values.push_back(IntValue(3));
Is "values" supposed to be the same object as "val"?
values.push_back(StringValue("test"));
for ( vector<Value>::iterator it = val.begin(); it != val.end();
++it ){
cout << *it;
}
}
I tried defining it empty in the base class and with a non-empty
implementation in IntValue and StringValue, but it gives me compile
errors (can't find the operator)
class Value{
std::ostream & operator<<(std::ostream & out){}
};
You're close. This would define a way to insert the stream into the
Value, rather than the other way around. Probably not what you want. ;)
So, how can I do it?
Define the operator as a free-standing function, and have it call a
print method declared in the base class.
You might want to adjust your viewpoint a bit when programming in C++.
When you consider a data type or operation, keep it as generic as
possible. Instead of defining two wrapper classes that are identical
except for the type of value they wrap, define a class template for such
wrappers. Instead of looping over the contents of a container (which
you are likely to do pretty frequently), use the existing library
facilities, and only write new code to represent the new or original
part of your program (not the looping code). You'll end up with lots of
short blocks and classes; it will feel inefficient, because you'll be
producing a lot of "overhead" code (class declarations and the like) for
each simple operation. However, the savings you will get in the long
term, not only in development time but in maintenance, might just
impress you so much with C++ that you find yourself using it for almost
everything.
Here's an example of one way to write something like the code above.
I've used explicit dynamic allocation, to match what I think you're
trying to do. Notice that the memory has to be deleted manually.
One other thing worth mentioning is that I already have built a library
of utility classes like Dereferencer and Deleter below, so I don't have
to redefine them every time I use them. If you're new to C++, but
already know a different language, this is probably a good time to start
building your own library of utilities.
#include <iosfwd>
struct Value_base
{
virtual ~Value_base ( ) { }
virtual void print ( std::ostream& ) const =0;
};
template< typename T >
struct Value: public Value_base
{
Value ( T const& t = T( ) ): m_t( t ) { }
void print ( std::ostream& out ) const { out << m_t; }
private:
T m_t;
};
std::ostream& operator << ( std::ostream& out, Value_base const& v )
{
v.print( out );
return out;
}
struct Dereferencer
{
template< typename T >
T const& operator ( ) ( T const* p ) const { return *p; }
};
struct Deleter
{
template< typename T >
void operator ( ) ( T const* p ) const { delete p; }
};
#include <algorithm>
#include <iostream>
#include <iterator>
#include <ostream>
#include <string>
#include <vector>
int main ( )
{
typedef std::vector< Value_base* > Vector;
Vector values;
values.push_back( new Value< int >( 3 ) );
values.push_back( new Value< std::string >( "test" ) );
std::ostream_iterator< Value_base > out( std::cout, "\n" );
Dereferencer deref;
std::transform( values.begin( ), values.end( ), out, deref );
std::for_each( values.begin( ), values.end( ), Deleter( ) );
}