views:

174

answers:

8

I need to store a list of various properties of an object. Property consists of a name and data, which can be of any datatype.

I know I can make a class "Property", and extend it with different PropertySubClasses which only differ with the datatype they are storing, but it does not feel right.

class Property
{
     Property(std::string name);
     virtual ~Property();

     std::string m_name;
};

class PropertyBoolean : Property
{
     PropertyBoolean(std::string name, bool data);

     bool m_data;
};

class PropertyFloat : Property
{
     PropertyFloat(std::string name, float data);

     float m_data;
};

class PropertyVector : Property
{
     PropertyVector(std::string name, std::vector<float> data);

     std::vector<float> m_data;
};

Now I can store all kinds of properties in a

 std::vector<Property*>

and to get the data, I can cast the object to the subclass. Or I can make a pure virtual function to do something with the data inside the function without the need of casting.

Anyways, this does not feel right to create these different kind of subclasses which only differ by the data type they are storing. Is there any other convenient way to achieve similar behavior?

I do not have access to Boost.

+2  A: 

I suggest boost::variant or boost::any. [Related question]

FredOverflow
Boost is not an option.
hasdf
+5  A: 

A lower-level way is to use a union

class Property
  union {
    int int_data;
    bool bool_data;
    std::cstring* string_data;
  };
  enum { INT_PROP, BOOL_PROP, STRING_PROP } data_type;
  // ... more smarts ...
};

Dunno why your other solution doesn't feel right, so I don't know if this way would feel better to you.

EDIT: Some more code to give an example of usage.

Property car = collection_of_properties.head();
if (car.data_type == Property::INT_PROP) {
  printf("The integer property is %d\n", car.int_data);
} // etc.

I'd probably put that sort of logic into a method of the class where possible. You'd also have members such as this constructor to keep the data and type field in sync:

Property::Property(bool value) {
  bool_data = value;
  data_type = BOOL_PROP;
}
Grumdrig
Could you elaborate? I do not understand this
hasdf
@hasdf: The union lets you store any of the constituent types, but only one at a time (basically all the members share the same region of memory). And then it's up to you to use the `data_type` field to keep track of what type is actually stored.
casablanca
+2  A: 

Write a template class Property<T> that derives from Property with a data member of type T

jdv
You still wouldn't be able to put them into an array or an stl container. Property<int> and Property<float> are entirely different types.
Dima
@Dima: Yes, but in the example they are stored by pointer anyway.
jdv
@Dima: Not if they have a common superclass, which I think is what jdv meant by "derives from `Property`".
Joey Adams
Right, you could have a collection of pointers to the base class. But then there is still a problem: to get the value of the property, you need to know which subclass it is.
Dima
@Dima: You can dynamic cast to find out. Usually you know which types you put in, so that's what you will be looking for. And in this case the properties have text labels. So he'll probable use a Date type to store the "Birthday" property.
jdv
Incomplete answer. To make this work properly (with erase/sort support) for any container you'll need smart pointers. Plus OP already did that...
SigTerm
@jdv: I know, but this still feels wrong. Usually when you have to ask an object what class it is, that is an indication of a design flaw.
Dima
This works very well so long as a common interface to your properties, like conversion to and from a string.
luke
A: 

You can probably do this with the Boost library, or you could create a class with a type code and a void pointer to the data, but it would mean giving up some of the type safety of C++. In other words, if you have a property "foo", whose value is an integer, and give it a string value instead, the compiler will not find the error for you.

I would recommend revisiting your design, and re-evaluating whether or not you really need so much flexibility. Do you really need to be able to handle properties of any type? If you can narrow it down to just a few types, you may be able to come up with a solution using inheritance or templates, without having to "fight the language".

Dima
A: 

Another possible solution is to write a intermediate class managing the pointers to Property classes:

class Bla {
private:
  Property* mp
public:
  explicit Bla(Property* p) : mp(p) { }

  ~Bla() { delete p; }

  // The standard copy constructor
  // and assignment operator
  // aren't sufficient in this case:
  // They would only copy the 
  // pointer mp (shallow copy)
  Bla(const Bla* b) : mp(b.mp->clone()) { }

  Bla& operator = (Bla b) { // copy'n'swap trick
    swap(b);
    return *this;
  }

  void swap(Bla& b) {
    using std::swap; // #include <algorithm>
    swap(mp, b.mp);
  }

  Property* operator -> () const {
    return mp;
  }

  Property& operator * () const {
    return *mp;
  }
};

You have to add a virtual clone method to your classes returning a pointer to a newly created copy of itself:

class StringProperty : public Property {
// ...
public:
  // ...
  virtual Property* clone() { return new StringProperty(*this); }
  // ...
};

Then you'll be able to do this:

std::vector<Bla> v;
v.push_back(Bla(new StringProperty("Name", "Jon Doe")));
// ...
std::vector<Bla>::const_iterator i = v.begin();
(*i)->some_virtual_method();

Leaving the scope of v means that all Blas will be destroyed freeing automatically the pointers they're holding. Due to its overloaded dereferencing and indirection operator the class Bla behaves like an ordinary pointer. In the last line *i returns a reference to a Bla object and using -> means the same as if it was a pointer to a Property object.

A possible drawback of this approach is that you always get a heap operation (a new and a delete) if the intermediate objects must be copied around. This happens for example if you exceed the vector's capacity and all intermediate objects must be copied to a new piece of memory.

In the new standard (i.e. c++0x) you'll be able to use the unique_ptr template: It

  • can be used inside the standard containers (in contrast to the auto_ptr which must not be used in the standard containers),
  • offers the usually faster move semantics (it can easily passed around) and
  • takes care over the held pointers (it frees them automatically).
phlipsy
+4  A: 
sbi
But `std::shared_ptr` isn't part of C++ yet. And if he isn't allowed to use Boost, he most likely won't be allowed to use the TR1 extensions nor C++0x either.
phlipsy
@phlipsy: Oh, I simply overlooked the statement about boost. That's dumb. Anyway, if a shop doesn't allow boost, they'll have to have their own smart pointer anyway - [as bad as this usually is](http://stackoverflow.com/questions/1437053/boost-advocacy-help-needed/1437748#1437748), it's still better than having no smart pointer. Also, some compilers already support `std::shared_ptr`.
sbi
What's the point of shared_ptr here compared to regular pointer?
hasdf
@hasdf: `std::shared_ptr` is a "smart pointer". It allows an arbitrary number of `std::shared_ptr` instances to share the same objects, but will automatically delete the object when the last `std::shared_ptr` instance referring to dies.
sbi
Well, can't I just easily use "delete" to delete the object?
hasdf
@hasdf: It's not the question whether you _can_, but whether you _have to_. I'll add an explanation to my answer, since this would be too long for a comment.
sbi
Clear answer. But, what if exceptions are not used at all? Then this is not a problem.
hasdf
@hasdf: You should read __all__ of my treatise. That's why I wrote it. (Add to that that the std lib sometimes throws exceptions. You do use the std lib, don't you?)
sbi
I do not use std.
hasdf
A: 

I see that there are lots of shots at trying to solve your problem by now, but I have a feeling that you're looking in the wrong end - why do you actually want to do this in the first place? Is there some interesting functionality in the base class that you have omitted to specify?

The fact that you'd be forced to switch on a property type id to do what you want with a specific instance is a code smell, especially when the subclasses have absolutely nothing in common via the base class other than a name (which is the type id in this case).

Johann Gerell
A: 

The property base class you mentioned "does not feel right" is the usual C++ object oriented method.

Jay