Design Issue: Derived classes need info about other derived classes

Hello everyone,

I've got an interesting situation. There are three classes [named "A", "B" and "C"], all derived from a abstract base class [named "base"]. However, in my program, I need to sort a vector of "base" object in a way such that objects of type A are less than B, which are less than C.

In order to achieve this, I need to add pure virtual function to the base class like "bool isA(), bool isB()" etc, which in the derived class would return the appropriate value.

However, this appear to me as a bit messy and perhaps I am doing something against the OOP philosophy, since if I would add a derived class D, I'd have to modify every class!

How would you handle this situation?

--
E
Last edited on
Exercised on a similar issue once, trying to sort a set of strings mixed with integers. I didn't come up with design that satisfies me entirely, because the two types of objects do not form a total order. You see, strings align on the first character and integers align on the last digit, so their orders do not inter operate easily.

Anyways. My point is, I find your approach interesting. Giving you my first thought, I would use a virtual function that returns a type index. Like in the code below.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Base
{

    friend bool operator < (const Base&, const Base&);

private:
    virtual int getTypeIndex() const = 0;
    virtual bool less_than(const Base&) const = 0;
};

bool operator < (const Base& left, const Base& right)
{
    int leftTypeIndex = left.getTypeIndex();
    int rightTypeIndex = right.getTypeIndex();

    if (leftTypeIndex != rightTypeIndex)
    {
        return leftTypeIndex < rightTypeIndex;
    }
    else
    {
        return left.less_than(right);
    }
}

template <typename T>
class Derived : public Base
{

public:
    Derived(const T &data) : m_data(data) {}

private:
    virtual int getTypeIndex() const { return m_type_index; }
    virtual bool less_than(const Base& right) const
    {
        return m_data < static_cast<const Derived&>(right).m_data;
    }

    static const int m_type_index;
    T m_data;
};

template<>
const int Derived<int>::m_type_index = 0;
template<>
const int Derived<char>::m_type_index = 1;

You can also use a member variable in the base class that is initialized differently for each derived type. It consumes a bit more memory in each object, while the previous approach uses only the vtable. Current optimizations tend to be more memory-wise and not so much speed-wise, so this is probably the inferior strategy. Here it is.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

class Base
{

    friend bool operator < (const Base&, const Base&);

protected:
    Base(int type_index) : m_type_index(type_index) {}

private:
    virtual bool less_than(const Base&) const = 0;

    const int m_type_index;
};

bool operator < (const Base& left, const Base& right)
{
    if (left.m_type_index != right.m_type_index)
    {
        return left.m_type_index < right.m_type_index;
    }
    else
    {
        return left.less_than(right);
    }
}

template <typename T>
class Derived : public Base
{

public:
    Derived(const T &data) : Base(m_type_index), m_data(data) {}

private:
    virtual bool less_than(const Base& right) const
    {
        return m_data < static_cast<const Derived&>(right).m_data;
    }

    static const int m_type_index;
    T m_data;
};

template<>
const int Derived<int>::m_type_index = 0;
template<>
const int Derived<char>::m_type_index = 1;

Use case for both solutions:
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>

int main()
{
    Derived<int> i(5);
    Derived<char> s(5);

    std::cout << (i < s) << ' ' << (s < i) << std::endl; // prints "1 0"

    std::cin.get();
}

Regards

EDIT: The additional consumed memory with the second approach would have been spared if C++ supported accessing static member variables (and functions) through the vtable, but alas no such mechanism is provided. :(
Last edited on
You solution assumes that the derived classes can be sorted/ordered the same way numbers can. However, in my case, the "ordering" does not have to follow such rules. For example, A<B<C, but a new class D can be considered to be "less" than A, but "greater" than C.

Nevertheless, the solution given will work in most of the cases, but I still do feel that they violate pure OOP rules in this extraordinary situation.
Hm, my opinion is that you aren't really using inheritance as it was designed for. Why do these classes need to be sorted like this?
Emir
, I agree with firedraco. You are trying to compare classes not objects, aren't you? That is the problem, I believe.

May some compile time programming help to initialize some static property for each class? Such technique is useful in overloading methods.



simeonz>accessing static member variables (and functions) through the vtable

What exactly are you talking about with virtual tables? It might be that I've missed something...

Why do you need any virtual table for a static member? :-)
They are static, they belong to class, not to the object. They can be resolved at compile time!

Here:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream>

struct baseClass {
    static int _Int;
    virtual ~baseClass() { };
};

int baseClass::_Int = 1;

struct derivedAClass: public baseClass {
    static int _Int;
    virtual ~derivedAClass() { };
};

int derivedAClass::_Int = 2;

struct derivedBClass: public baseClass {
    virtual ~derivedBClass() { };
};

int main() {
       
    std::cout << baseClass::_Int << std::endl;
    std::cout << derivedAClass::_Int << std::endl;
    std::cout << derivedBClass::_Int << std::endl;

    return 0;
}


I've overridden (so ridiculous it sounds...) the static variable :-) No virtual table involved...

upd:
In fact, no overriding has taken place above, I've being kidding, of cause. I've only masked the static variable. Nevertheless, I've achieved the goal: the same static interface behaves in a different way for the different classes.

There is a special term: compile time polymorphism, usually one use it to indicate that one declaration of a class can be compiled in a different way. Generics and templates give programmer such an opportunity.
Last edited on
Emir wrote:
You solution assumes that the derived classes can be sorted/ordered the same way numbers can. However, in my case, the "ordering" does not have to follow such rules. For example, A<B<C, but a new class D can be considered to be "less" than A, but "greater" than C.

I may have misunderstood you, but I think the solution will work when the set of derived classes can be ordered in a sequence.

I think you criticize this code:
1
2
3
4
template<>
const int Derived<int>::m_type_index = 0;
template<>
const int Derived<char>::m_type_index = 1;
I never assumed that the above lines will not need special maintenance. Suppose, you want to create a new wrapper for class Derived<short> and you want this new derived class to be between the classes Derived<int> and Derived<char> in the ordering. Then you will need to modify the code like this:
1
2
3
4
5
6
template<>
const int Derived<int>::m_type_index = 0;
template<>
const int Derived<char>::m_type_index = 1;
template<>
const int Derived<char>::m_type_index = 12;


lionishy wrote:
Why do you need any virtual table for a static member? :-)
They are static, they belong to class, not to the object. They can be resolved at compile time!
I know what you are saying, but you indeed missed my point. Static variables indeed belong to the type. But the dynamic type of an object belongs to the object. That is the idea behind polymorphism and the vtable. Classically, the vtable is used in order to be able to resolve a call on some object based on its dynamic type. Something like dynamic overload resolution. But this dynamic type does not need to be used only for resolving calls in this way. It can be used for storing other type specific information. Like - what is the value of some constant, what is the address of some function, etc. For example, suppose the following language extension:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct A {
virtual static const int type_specific_value = 0;
};

struct B : A {
virtual static const int type_specific_value;
};
const int B::type_specific_value = 1;


struct C : A {
virtual static const int type_specific_value;
};
const int C::type_specific_value = 2;

int main()
{
B b;
C c;
A *p1 = &b;
A *p2 = &c;
cout << p1->type_specific_value << endl; //prints 1
cout << p2->type_specific_value << endl; //prints 2
}

The same can be used for static functions. As a matter of fact, currently the dynamic type is accessible only through an object instance, because it is only used for object-level methods. I argue that it should be possible to create the dynamic type as a separate meta-object allowing access to class level methods, resolved dynamically. Essentially, a person can always use function pointers for a single static function or object pointers for static member variable, but if you need a bunch of them, like an interface, it is inconvenient. I don't mean support for run-time introspection here. I only say, if we have the concept of dynamic type, why don't we allow all members of a type to benefit from run-time binding? And why do we keep the dynamic type as implicit instance data? Why don't we simply recognize it a separate entity that is automatically associated with an instance, but can be used by itself?

Regards
Last edited on
simeonz


Look closer to the code you've written.

The line 18 says: create reliable interface "B" with the implementation by type "B" and place one inside the object named "b".

The line 20 says:create reliable interface "A" and take implementation from the object "b".

And you expect on the line 22 to gain some "b" specific information. That is not good... At the 18th line you've trusted only "A" and now you change your mind...

There are to ways to cope with contradiction: either "A" has to provide additional trusted method, which can be overridden for every specific type, or there has to be some instrument to resolve the specific type of implementation like introspection.

If you look closer to your code, you'll find that you prefer the first way. All you need to do is to provide legal virtual method according to the C++ rules.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct A {
    static int type_specific_value;
    virtual int reflection() { return A::type_specific_value; }
    
    virtual ~A() { };
};

int A::type_specific_value = 0;

struct B : A {
    static int type_specific_value;
    virtual int reflection() { return B::type_specific_value; }
    
    virtual ~B() { };
};
int B::type_specific_value = 1;




You are talking about dynamic type. However, there is no any, from my point of view. What you refer to as a dynamic type is a type of object implementing trusted interface. It's just type of the object, normal static type, but unknown one, `cause you don't care, moreover, you don't trust one (otherwise you've asked for "B" interface
B * p1 = &b;
).

That is my opinion of the issue under discussion.


lionishy, you try to interpret the matter conceptually. You focus on the use of pointers as interfaces. Ok, but there is a lot going on underneath the implementation.

The virtual table holds data that can be shared between all instances of specific type, program wide. It is usually a const structure implicitly generated by the compiler. What I would like to be able to do is to add additional constant to the vtable. The virtual table currently only holds the entry points to virtual object level methods. But I want some constant of my own whose value I should be able to override in the derived classes. This will be much faster than performing a method call through the vtable, just to return the value of some class level data - come on..

Also, figuratively speaking, those implicit virtual table objects, in practice, have structures that form a class hierarchy running in parallel to the class hierarchy of the types they service. Class Base and class Derived have corresponding DerivedVTable and BaseVTable, and DerivedVTable is in a sense derived from BaseVTable. I just would like to have more control over those objects. I want to be able to insert virtual static constants in BaseVTable and override it in DerivedVTable and use this data in polymorphic way, from within the implementation of my methods. I don't intend to expose virtual data in the public interface, but I want to increase the performance of the implementation behind the public interface. Also, I would like direct access to the VTable hierarchy. I do not see a reason, why I should not be allowed to create BaseVTable and DerivedVTable objects (they may contain non-const data too, for non-const virtual static fields), assign addresses of DerivedVTable objects to BaseVTable, etc. Then, if the language permitted virtual static methods, they could also be called directly through BaseVTable pointers. Why not?

Regards
simeonz

I have to confess, I've been considering virtual table as some instrument, tool to maintain dynamic method dispatching. So, I really missed the point with expanding vtable to support something beyond overridden interface.
Yes, I knew that this was the case. I threw in the idea very lightly and without context.
Topic archived. No new replies allowed.