Reputation: 6561
I have a class that can generically hold any (primitive) type:
class Value
{
private:
int i_value;
unsigned int ui_value;
long l_value;
unsigned long ul_value;
short s_value;
float f_value;
double d_value;
char c_value;
bool b_value;
std::string str_value;
int type;
void setValue(int value);
void setValue(unsigned int value);
void setValue(long value);
void setValue(unsigned long value);
void setValue(short value);
void setValue(float value);
void setValue(double value);
void setValue(char value);
void setValue(bool value);
void setValue(std::string value);
public:
Value(int value);
Value(unsigned int value);
Value(long value);
Value(unsigned long value);
Value(short value);
Value(float value);
Value(double value);
Value(char value);
Value(bool value);
Value(std::string value);
Value(Value& other); //Copy Constructor
~Value();
int getType();
std::string toString(int format);
};
This is nice, because I can do something like:
Value * v1 = new Value(55);
Value * v2 = new Value(1.2);
Value * v3 = new Value("yes");
Value * v4 = new Value(true);
However, as you can see, it's pretty ugly; tons of overloading of everything to make it work. I was thinking templates could make this generic. However, as far as I can tell, you always have to specify the type, which sort of defeats the whole purpose of the class.
For example:
Value<int> * v1 = new Value<int>(55);
Value<double> * v2 = new Value<double>(1.2);
Value<string> * v3 = new Value<string>("yes");
Value<bool> * v4 = new Value<bool>(true);
If I use templates, I can no longer do something like vector<Value *>
like I could before. Is this correct, or am I missing some aspect of templates that could help in this situation?
Upvotes: 2
Views: 1001
Reputation: 5665
All you need is a parent base class for the template one:
class BaseValue
{
public:
virtual ~BaseValue()
{}
};
template<typename T>
class Value : public BaseValue
{
public:
Value(const T& value)
:m_value(value)
{}
void set(const T& value)
{
m_value = value;
}
const T& get()
{
return m_value;
}
virtual ~Value()
{}
private:
T m_value;
};
std::vector<BaseValue*> values;
values.push_back(new Value<int>(1)); // int
values.push_back(new Value<double>(1.0)); // double
values.push_back(new Value<char*>("asdf")); // pointer to array on stack :(
values.push_back(new Value<char>('c')); // char
Upvotes: 4
Reputation: 187
First question: Use a template factory function instead of new.
Second question: Use a common base-class.
In order to be able to properly delete the objects pointed to by the pointers on the vector, you need a virtual destructor. Also, in order to do anything useful with the pointers to the base class, you need virtual methods in the base class.
Example:
class ValueBase
{
public:
virtual ~ValueBase() = default;
virtual void Print(std::ostream & os) const = 0;
};
std::ostream & operator<< (std::ostream & os, const ValueBase & value)
{
value.Print(os);
return os;
}
template<typename T> class Value : public ValueBase
{
T value;
public:
Value(const T & v) : value(v) {}
const T & Get() const;
void Set(const T & v);
void Print(std::ostream & os) const
{
os << value;
}
// ...
};
template<typename T> Value<T> * NewValue(const T & v)
{
return new Value<T>(v);
}
Now you can do
ValueBase * v1 = NewValue(55);
ValueBase * v2 = NewValue(1.2);
ValueBase * v3 = NewValue<std::string>("yes");
ValueBase * v4 = NewValue(true);
std::vector<ValueBase *> vec;
vec.push_back(v1);
vec.push_back(v2);
vec.push_back(v3);
vec.push_back(v4);
vec.push_back(NewValue(2350983444325345ll));
for (const auto & entry : vec)
{
std::cout << *entry << " ";
}
std::cout << "\n";
Note that you normally do not need the template argument of NewValue, since it will be deduced. With "yes"
this does not work, since Value<T>
will be instantiated with T = char [4]
which would require you to use strcpy
in the constructor. I find it quite nice to explicity state that a conversion is needed as above. If you prefer this implicitly, do an overload:
Value<std::string> * NewValue(const char * v)
{
return new Value<std::string>(v);
}
Make sure to delete the contents manually
for (const auto & entry : vec)
{
delete entry;
}
vec.clear();
or use std::unique_ptr
instead of the bare pointer:
template<typename T> std::unique_ptr<Value<T>> UniqueValue(const T & v)
{
return std::unique_ptr<Value<T>>(new Value<T>(v));
}
std::vector<std::unique_ptr<ValueBase>> vec;
vec.push_back(NewValue(4.5));
If you extend Value
and it turns out you need to do something in the destructor, you have to implement the copy constructor, assignment operator and possibly also the move constructor and move assignment operator. ("Rule of Three" or "Rule of Five".) In the above version the "Rule of Zero" still applies, since the destructor is still the same as the implicitly defined one (= default
).
If you add the destructor it should be virtual. Otherwise you might get memory leaks if you delete the VirtualBase
pointers.
Upvotes: 3
Reputation: 16089
Of course you can do templates, but you also need to do polymorphic.
class StupidAndEmpty {} // add virtual destructor if Value needs a destructor
template<class dType>
Value : StupidAndEmpty {
// do smart things with dType
}
vector<StupidAndEmpty *> notSoStupid;
Only problem is how do you use them when you get them back from vector.
Upvotes: 1