tags:

views:

141

answers:

3

Hi,

I'm building an hierarchy of objects that wrap primitive types, e.g integers, booleans, floats etc, as well as container types like vectors, maps and sets. I'm trying to (be able to) build an arbitrary hierarchy of objects, and be able to set/get their values with ease. This hierarchy will be passed to another class (not mentioned here) and an interface will be created from this representation. This is the purpose of this hierarchy, to be able to create a GUI representation from these objects.To be more precise, i have something like this:

class ValObject 
{
    public:
        virtual ~ValObject() {}
};

class Int : public ValObject 
{
    public:
        Int(int v) : val(v) {}
        void set_int(int v) { val = v);
        int get_int() const { return val; } 
    private:
        int val; 
};

// other classes for floats, booleans, strings, etc
// ...

class Map : public ValObject {}
{
    public:
         void set_val_for_key(const string& key, ValObject* val);
         ValObject* val_for_key(const string& key);
    private:
         map<string, ValObject*> keyvals;
};

// classes for other containers (vector and set) ... 

The client, should be able to create and arbitrary hierarchy of objects, set and get their values with ease, and I, as a junior programmer, should learn how to correctly create the classes for something like this.

The main problem I'm facing is how to set/get the values through a pointer to the base class ValObject. At first, i thought i could just create lots of functions in the base class, like set_int, get_int, set_string, get_string, set_value_for_key, get_value_for_key, etc, and make them work only for the correct types. But then, i would have lots of cases where functions do nothing and just pollute my interface. My second thought was to create various proxy objects for setting and getting the various values, e.g

class ValObject
{
    public:
         virtual ~ValObject() {}    
         virtual IntProxy* create_int_proxy(); // <-- my proxy
};

class Int : public ValObject
{
    public:
         Int (int v) : val(v) {}
         IntProxy* create_int_proxy() { return new IntProxy(&val); }
    private:
         int val;
};

class String : public ValObject
{
    public:
         String(const string& s) : val(s) {}
         IntProxy* create_int_proxy() { return 0; }
  private:
         string val;
};

The client could then use this proxy to set and get the values of an Int through an ValObject:

ValObject *val = ... // some object
IntProxy  *ipr = val->create_int_proxy();
assert(ipr); // we know that val is an Int (somehow)
ipr->set_val(17);

But with this design, i still have too many classes to declare and implement in the various subclasses. Is this the correct way to go ? Are there any alternatives ?

Thank you.

+3  A: 

Take a look at boost::any and boost::variant for existing solutions. The closest to what you propose is boost::any, and the code is simple enough to read and understand even if you want to build your own solution for learning purposes --if you need the code, don't reinvent the wheel, use boost::any.

David Rodríguez - dribeas
Boost::any is not the same solution. Boost::any is a value-type solution- his is polymorphic. The two are quite different.
DeadMG
@DeadMG: Take a look at the implementation of boost::any. It is in fact implemented very similarly to what he proposes. (Hand waving and ignoring optimization and details) The `any` object keeps a pointer to a base class that only offers a virtual destructor and a method to identify the type. The `any_cast` checks that the type is correct and performs a `dynamic_cast` of the internal pointer to the derived type (actual derived types are instantiations of a template)
David Rodríguez - dribeas
@David: That's not going to help him when he wants to make a new Int and pass that pointer around. Because boost::any is a value, and he needs a reference.
DeadMG
@DeadMG: I don't quite follow. If the question is: *The main problem I'm facing is how to set/get the values through a pointer to the base class* an answer is *check the `boost::any` implementation* because that is precisely done there. Then if the question is how to build a wrapper that can hold unrelated types, an answer is again, *check the `boost::any` implementation*. That is not the only answer, there are others including variants -- your solution resembles boost::variant where instead of an static visitor you require to manually implement `PerformOperationByFunctor`.
David Rodríguez - dribeas
But again, `boost::any` is more in the line of the solution he already has so it would be easier to read/follow. On the issue of `boost::any` being a value type, you can pass references/pointers to `boost::any` as easily as you can pass references/pointers to other types. And I believe that the requirement of accessing through pointers is not a requirement of the problem, but an issue with his proposed solution/implementation.
David Rodríguez - dribeas
Thank you, I'll take a look at boost, but I'm afraid the code will be too complicated for me. Thank you anyway.
GavinTael
The code in boost::any is rather simple. `placeholder` takes the position of your `ValObject` base, with `holder` being the derived types --templated--, and `any` being a wrapper to hold the pointer and offer a simple interface.
David Rodríguez - dribeas
@DeadMG "Because boost::any is a value, and he needs a reference." boost::any can store anything. If needed, a pointer can be stored or even a shared_ptr to something. It does use a copy-in policy and simply copies whatever is stored in any's copy ctor, but like a standard container, that doesn't restrict one from storing pointers, e.g. The main difference with boost::any is that it can allow deep copy semantics should T be stored instead of, say, shared_ptr<T>, and that it's a non-intrusive solution which can truly store anything instead of just subclasses of some generalized base object.
A: 

Use dynamic_cast to cast up the hierarchy. You don't need to provide an explicit interface for this - any reasonable C++ programmer can do that. If they can't do that, you could try enumerating the different types and creating an integral constant for each, which you can then provide a virtual function to return, and you can then static_cast up.

Finally, you could consider passing a function object, in double-dispatch style. This has a definite encapsulation advantage.

struct functor {
    void operator()(Int& integral) {
        ...
    }
    void operator()(Bool& boo) {
        ...
    }
};
template<typename Functor> void PerformOperationByFunctor(Functor func) {
    if (Int* ptr = dynamic_cast<Int*>(this)) {
        func(*ptr);
    }
    // Repeat
}

More finally, you should avoid creating types where they've basically been already covered. For example, there's little point providing a 64bit integral type and a 32bit integral type and ... it's just not worth the hassle. Same with double and float.

DeadMG
This does not really scale with the *should be able to create and arbitrary hierarchy of objects* requirement. You are forcing the clients to rewrite `PerformOperationByFunctor` to add each type of the hierarchy, and it is also error prone in that if there are objects at different levels of the hierarchy (car, ford, ford mondeo) the user must be careful to provide the `if/else if` blocks ordered from most detailed to less detailed types in the hierarchy.
David Rodríguez - dribeas
I'm familiar with this pattern, but i don't know how much of a burden will be compared to my implementation. What would a client prefer ?
GavinTael
The client is writing his internal functions now? PerformOperationByFunctor exists in ValObject*. Besides, there's no need for it to exist in the first place. Dynamic_Cast accomplishes this just fine.
DeadMG
A: 

One of the beauties of C++ is that these kinds of intrusive solutions often aren't necessary, yet unfortunately we still see similar ones being implemented today. This is probably due to the prevalence of Java, .NET, and QT which follows these kinds of models where we have a general object base class which is inherited by almost everything.

By intrusive, what's meant is that the types being used have to be modified to work with the aggregate system (inheriting from a base object in this case). One of the problems with intrusive solutions (though sometimes appropriate) is that they require coupling these types with the system used to aggregate them: the types become dependent on the system. For PODs it is impossible to use intrusive solutions directly as we cannot change the interface of an int, e.g.: a wrapper becomes necessary. This is also true of types outside your control like the standard C++ library or boost. The result is that you end up spending a lot of time and effort manually creating wrappers to all kinds of things when such wrappers could have been easily generated in C++. It can also be very pessimistic on your code if the intrusive solution is uniformly applied even in cases where unnecessary and incurs a runtime/memory overhead.

With C++, a plethora of non-intrusive solutions are available at your fingertips, but this is especially true when we know that we can combine static polymorphism using templates with dynamic polymorphism using virtual functions. Basically we can generate these base object-derived wrappers with virtual functions on the fly only for the cases in which this solution is needed without pessimizing the cases where this isn't necessary.

As already suggested, boost::any is a great model for what you want to achieve. If you can use it directly, you should use it. If you can't (ex: if you are providing an SDK and cannot depend on third parties to have matching versions of boost), then look at the solution as a working example.

The basic idea of boost::any is to do something similar to what you are doing, only these wrappers are generated at compile-time. If you want to store an int in boost::any, the class will generate an int wrapper class which inherits from a base object that provides the virtual interface required to make any work at runtime.

The main problem I'm facing is how to set/get the values through a pointer to the base class ValObject. At first, i thought i could just create lots of functions in the base class, like set_int, get_int, set_string, get_string, set_value_for_key, get_value_for_key, etc, and make them work only for the correct types. But then, i would have lots of cases where functions do nothing and just pollute my interface.

As you already correctly deduced, this would generally be an inferior design. One tell-tale sign of inheritance being used improperly is when you have a lot of base functions which are not applicable to your subclasses.

Consider the design of I/O streams. We don't have ostreams with functions like output_int, output_float, output_foo, etc. as being directly methods in ostream. Instead, we can overload operator<< to output any data type we want in a non-intrusive fashion. A similar solution can be achieved for your base type. Do you want to associate widgets with custom types (ex: custom property editor)? We can allow that:

shared_ptr<Widget> create_widget(const shared_ptr<int>& val);
shared_ptr<Widget> create_widget(const shared_ptr<float>& val);
shared_ptr<Widget> create_widget(const shared_ptr<Foo>& val);
// etc.

Do you want to serialize these objects? We can use a solution like I/O streams. If you are adapting your own solution like boost::any, it can expect such auxiliary functions to already be there with the type being stored (the virtual functions in the generated wrapper class can call create_widget(T), e.g.

If you cannot be this general, then provide some means of identifying the types being stored (a type ID, e.g.) and handle the getting/setting of various types appropriately in the client code based on this type ID. This way the client can see what's being stored and deal set/get values on it accordingly.

Anyway, it's up to you, but do consider a non-intrusive approach to this as it will generally be less problematic and a whole lot more flexible.