tags:

views:

93

answers:

3

I have a c++ project being developed in QT. The problem I'm running in to is I am wanting to have a single base class that all my property classes inherit from so that I can store them all together. Right now I have:

class AbstractProperty
        {
        public:
            AbstractProperty(QString propertyName);
            virtual QString toString() const = 0;
            virtual QString getName() = 0;
            virtual void fromString(QString str) = 0;
            virtual int toInteger() = 0;
            virtual bool operator==(const AbstractProperty &rightHand) = 0;
            virtual bool operator!=(const AbstractProperty &rightHand) = 0;
            virtual bool operator<(const AbstractProperty &rightHand) = 0;
            virtual bool operator>(const AbstractProperty &rightHand) = 0;
            virtual bool operator>=(const AbstractProperty &rightHand) = 0;
            virtual bool operator<=(const AbstractProperty &rightHand) = 0;
        protected:
            QString name;
        };

then I am implementing classes such as PropertyFloat and PropertyString and providing implementation for the comparator operators based on the assumption that only strings are being compared with strings and so on. However the problem with this is there would be no compiletime error thrown if i did

 if(propertyfloat a < propertystring b)

however my implementation of the operators for each derived class relies on them both being the same derived class. So my problem is I cant figure out how to implement a property structure so that I can have them all inherit from some base type but code like what I have above would throw a compile time error.

Any ideas on how this can be done? For those familiar with QT I tried using also a implementation with QVariant however QVariant doesn't have operators < and > defined in itself only in some of its derived classes so it didn't work out.

What my end goal is, is to be able to generically refer to properties. I have an element class that holds a hashmap of properties with string 'name' as key and the AbstractProperty as value. I want to be able to generically operate on the properties. i.e. if I want to get the max and min values of a property given its string name I have methods that are completely generic that will pull out the associated AbstactProperty from each element and find the max/min no matter what the type is. so properties although initially declared as PropertyFloat/PropertyString they will be held generically.

+5  A: 

What if instead of making the comparator operators members of the class you make them global functions?

If you did that, then you could make an operator for each given type and control which types can compare to each other:

bool operator==(const PropertyFloat &leftHand, const PropertyFloat &rightHand);
bool operator==(const PropertyString &leftHand, const PropertyString &rightHand);

The compiler would complain anytime that you did this:

if(propertyfloat a == propertystring b)

Now, in order to access the private data necessary for the comparison, make these global functions "friends" of the derived classes.

J Higs
+1 for suggesting global functions. Global functions may also be generalized using templates.
Thomas Matthews
This is agood approach and I might end up having to go this route but my main hesitation is that then I can no longer hold all of the information generically in my element class. Or if i hold it generically as the base class then i have to have some large swtich casting statements to get evertying to its correct class in order to utalize the operators.
Without Me It Just Aweso
Perhaps you should review your design. Equality, and ordering comparisons should only be performed on specific (leaf) classes.
Thomas Matthews
+2  A: 

I had a similar problem in one of my designs until I meditated over it. I realized that having comparison operators in the Base class was wrong. As you have found out, a comparison method in the base class is comparing any combination of descendant classes. This is bad Karma.

A better approach is to define the comparison operators in the descendant classes. This will help the compiler prevent people using the operators with different types. The side effect of this is that one can't compare objects by dereferencing a pointer to the base class (which will be a good thing).

Another useful tool are the Boost comparison templates. Look for equality_comparable.

Thomas Matthews
If i end up not having the comparisions declared in the baseclass and I constantly have to cast to the descendant classes. This means large switch/case statements when ever I want to do a comparison to determine which type to cast down to. How did you approach this? I really want to keep that my element class just has a hasmap of properties so we can have any type of property but that requires it being stored generically as the base.
Without Me It Just Aweso
I only pass base class pointers when absolutely necessary. There are few cases where I have to downcast. Also, the only "switch / case" statements I have are in Factories. I try to propagate generic code into base or parent classes. I have generic `Field` and `Record` classes that make database interface simpler. I have specific `Recipe`, `Ingredient` and `Menu` classes that are more specific. To use the database, `Ingredient` inherits from `Record`, so the database treats it as a record, not needing to know the specifics.
Thomas Matthews
+1  A: 

Another solution is to use the Curiously Recurring Template Pattern. This allows a templated class to define comparison operators based on a derived class.

Example:

template <class Descendant>
struct Numeric_Field
{
    Descendant    m_value;

    bool   operator==(const Descendant& d)
    {
        return m_value == d.value;
    }
    bool   operator!=(const Descendant& d)
    {
        return !(*this == d);
    }
    bool   operator< (const Descendant& d)
    {
        return m_value < d.m_value;
    }
    bool   operator<=(const Descendant& d)
    {
        return (*this < d) || (*this == d);
    }
    bool   operator> (const Descendant& d)
    {
        return !(*this <= d);
    }
    bool   operator>=(const Descendant& d)
    {
        return !(*this < d);
    }

protected:
    Numeric_Field(const Descendant& new_value = 0)
    :  m_value(new_value)
    { ;}
};

This could be made a little more generic by replacing m_value by using pure virtual protected setters and getters:

template <class Descendant_Type>
struct Numeric_Field_2
{
    virtual const Descendant_Type    get_value(void) const = 0;
    virtual void                     set_value(const Descendant& new_value) = 0;

    bool operator==(const Descendant_Type& dt)
    {
        return get_value() == dt.get_value();
    }
    bool operator< (const Descendant_Type& dt)
    {
        return get_value() < dt.get_value();
    }
};

At this point, you could pass around pointers to Numeric_Field wherever a comparison is needed. I believe this is only a typing saver.

Thomas Matthews