7

Suppose I have two structures a and b, each hold several variable in them (most of the variable are c++ core types but not all).

Is there a way to create a a pointer named c that can point to either one of them? Alternatively, is there a way to create a set that can hold either one of them?

Thanks

Yotam
  • 10,295
  • 30
  • 88
  • 128

8 Answers8

5

The usual way to create a pointer that can point to either of the two is to make them inherit from a common base-class. Any pointer of the base-class can point to any sub-class. Note that this way you can only access elements that are part of the base-class through that pointer:

class Base {
public:
    int a;
};

class Sub1 : public Base {
public:
    int b;
};

class Sub2 : public Base {
public:
    int c;
};


int main() {
    Base* p = new Sub1;
    p.a = 1; // legal
    p.b = 1; // illegal, cannot access members of sub-class
    p = new Sub2; // can point to any subclass
}

What you are trying to achieve is called polymorphism, and it is one of the fundamental concepts of object oriented programming. One way to access member of the subclass is to downcast the pointer. When you do this, you have to make sure that you cast it to the correct type:

static_cast<Sub1*>(p).b = 1; // legal, p actually points to a Sub1
static_cast<Sub2*>(p).c = 1; // illegal, p actually points to a Sub1

As for your second question, using the technique described above, you can create a set of pointers to a base-class which can then hold instance of any of the subclasses (these can also be mixed):

std::set<Base*> base_set;
base_set.insert(new Sub1);
base_set.insert(new Sub2);
Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
  • 1
    `dynamic_cast(p)` will cause undefined behavior (`Base` is not a polymorphic class type). I think you meant `static_cast`. – CB Bailey Sep 05 '11 at 07:22
  • @Björn Pollex: Is there a way for me to check if a member of Base is sub1 or sub2 (other than creating a specific variable in the class)? – Yotam Sep 05 '11 at 12:10
  • @Yotam: If your base-class has at least one `virtual` method, you can use `dynamic_cast`. If the cast is invalid, the result will be a null-pointer. – Björn Pollex Sep 05 '11 at 12:15
  • @Björn Pollex It doesn't have any method and reading some of the comments about similar question (some of them are yours) I realized that his is a bad idea (Which is logical) I'll try and do this without any checking... – Yotam Sep 05 '11 at 12:18
4

Alternatively, is there a way to create a set that can hold either one of them?

Take a look at Boost.Any and Boost.Variant. If you have just 2 classes, then variant should suffice. If you plan other types, and don't want to recompile this 'set', then use any.

Then use any container of either any or variant.

#include <boost/any.hpp>
#include <boost/variant.hpp>

#include <vector>

class A { };
class B { };
class C { };

int main()
{
    // any

    std::vector<boost::any> anies;
    anies.push_back(A());
    anies.push_back(B());

    A a0 = boost::any_cast<A>(anies[0]);
    A b0 = boost::any_cast<A>(anies[1]); // throws boost::bad_any_cast

    // variant
    std::vector<boost::variant<A,B> > vars;
    vars.push_back(A());
    vars.push_back(B());

    A a1 = boost::get<A>(vars[0]);
    A b1 = boost::get<A>(vars[1]); // throws boost::bad_get

    // and here is the main difference:
    anies.push_back(C()); // OK
    vars.push_back(C());  // compile error
}

Edit: having more than 2 classes is of course possible for variant, too. But extending variant so it is able to hold a new unanticipated type without recompilation is not.

Alexander Poluektov
  • 7,844
  • 1
  • 28
  • 32
3

If a and b are unrelated, then you can use a void* or, better, a boost any type.

If a is superclass of b, you can use an a* instead.

Simone
  • 11,655
  • 1
  • 30
  • 43
  • Although this answer is technically correct, I think it's bad advise. – xtofl Sep 05 '11 at 07:18
  • @duedl0r: I know nothing better:) Please downvote it if you don't like it - but enlighten me about the why if you please. – xtofl Sep 05 '11 at 07:30
  • 1
    @xtofl, IMHO it's just that you shouldn't use OO for this purpose.. I think it's as bad as using `void*`.. :) – duedl0r Sep 05 '11 at 07:32
1

If they both inherit from the same type you can do it. Thats how OOP frameworks work, having all classes inherit from Object.

Max
  • 8,671
  • 4
  • 33
  • 46
  • Definitely *not* all OOP frameworks have a common baseclass for everything. And there is nothing inherently OOP about doing that (or about having lots of classes, either). – Christopher Creutzig Sep 05 '11 at 07:19
0

Abstract Class !!!! -- simple solutions

To have a base class that can be used as a pointer to several derived sub classes. (no casting needed)

Abstract class is define when you utilize a virtual method in it. Then you implement this method in the sub-class... simple:

// abstract base class
#include <iostream>
using namespace std;

class Polygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
    virtual int area (void) =0;
};

class Rectangle: public Polygon {
  public:
    int area (void)
      { return (width * height); }
};

class Triangle: public Polygon {
  public:
    int area (void)
      { return (width * height / 2); }
};

int main () {
  Polygon * ppoly1 = new Rectangle (4,5);
  Polygon * ppoly2 = new Triangle (4,5);
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  cout << ppoly1->area() << '\n';
  cout << ppoly2->area() << '\n';
  return 0;
}
Gilco
  • 1,326
  • 12
  • 13
0

Just define a common superclass C and two subclasses A, B of C. If A and B have no common structure (no common attributes), you can leave C empty.

The define:

A *a = new A();
B *b = new B();
C *c;

Then you can do both

c = a;

or

c = b;
Giorgio
  • 5,023
  • 6
  • 41
  • 71
  • 3
    I won't downvote, but inheritance should be used because it makes sense in a logical matter, not because of technical reasons. If `A` and `B` are unrelated, why would you make them inherit from the same superclass ? And what would that `class` represent ?! – ereOn Sep 05 '11 at 07:13
  • Actuaclly there is a logical reason to do so, both ``a`` and ``b`` are particles. I'll read a little about inhetitence – Yotam Sep 05 '11 at 07:15
  • Well, if you want to use one type of pointer for both structures, they must have something in common in that context. Or am I missing something? – Giorgio Sep 05 '11 at 07:15
  • 1
    @Giorgio: The OP precised that his classes have indeed something in common, so the solution fits here. I just don't like the rationale in your answer: one should first define the logical pattern to solve his problem, **then** choose appropriate technical solution, not the other way around. – ereOn Sep 05 '11 at 07:22
  • @ereOn: The superclass models the fact that you have objects of class A and objects of class B that you want to group together (e.g. in a common set). If A and B have nothing in common, then C is the (disjoint) union of A and B. – Giorgio Sep 05 '11 at 07:23
  • And what attributes, members, methods, behavior do those two classes share ? Moreover, if you refer to your classes using a base class pointer, you probably will have to use RTTI for the sole reason of solving a technical problem. – ereOn Sep 05 '11 at 07:27
  • @ereOn: Maybe I did not express it correctly, but it was not intended to be a workaround or a merely technical solution. If I want to group objects from A and B together, then I think of them as belonging to a common superclass. With the void * solution the variable c can point to just anything. With subclassing it can only point to A or B. – Giorgio Sep 05 '11 at 07:29
  • @ereOn: What do you find so disturbing in the fact that they (may) have an empty intersection? Think about the Object class in Java. – Giorgio Sep 05 '11 at 07:30
  • @Giorgio: This is one reason why I don't like Java ;) More seriously, If the classes have something in common, then it's allright and I obviously agree with the solution. The fact is, there is already a way to store two unrelated classes, structures or even native types: `void*`. If you create another abstract common super-baseclass, then **every** object in your codebase should derive from it (because that's what the class is: a *common* super-baseclass). And if you do so, you are just reinventing Java. – ereOn Sep 05 '11 at 07:35
  • What they have in common is that instances of both classes need to be added to a common set. – Giorgio Sep 05 '11 at 13:21
  • @Giorgio: Well I need to put some `int` and `float` and `std::vector` into the same set. Following your rationale, should I modify the standard library to make them derive from the same superclass ? – ereOn Sep 05 '11 at 19:42
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/3190/discussion-between-giorgio-and-ereon) – Giorgio Sep 05 '11 at 19:51
  • I have found the following in another answer http://en.wikipedia.org/wiki/Marker_interface_pattern. – Giorgio Sep 26 '11 at 10:41
  • Here is the link to the answer: http://stackoverflow.com/questions/7552677/are-empty-interfaces-code-smell – Giorgio Sep 26 '11 at 12:07
0

Although you can do that, what would that pointer mean? If any portion of your application gets hold on the pointer to 'either a or b', it cannot do a lot with it, unless you provide extra type information.

Providing extra type information will result in client code like

if( p->type == 'a' ) {
   ... a-specific stuff
} else if( p->type == 'b' ) {
   ... b-specific stuff
} ...

Which isn't very useful.

It would be better to delegate 'type-specificness' to the object itself, which is the nature of object-oriented design, and C++ has a very good type-system for that.

class Interface {
 public:
    virtual void doClientStuff() = 0; // 
    virtual ~theInterface(){};
};

class A : public Interface {
    virtual void doClientStuff(){ ... a-specific stuff }
};

class B : public Interface {
    virtual void doClientStuff(){ ... b-specific stuff }
};

And then your client code will become more type-unaware, since the type-switching is done by C++ for you.

void clientCode( Interface* anObject ) {
   anObject->doClientStuff();
}

Interface* i = new A();
Interface* j = new B();

clientCode( i );
clientCOde( j );
xtofl
  • 40,723
  • 12
  • 105
  • 192
  • And now the OP will have to change the whole design of his classes (and to use a vtable) just to solve his storage problem. – ereOn Sep 05 '11 at 07:29
  • @ereOn: apparently, he already has a Particle concept. The fact that his `a` and `b` are not modeled as `Particle` shows a _modeling_ problem, _causing_ a storage problem. Solving this problem when he encounters a technical problem is just paying the technical debt. – xtofl Sep 05 '11 at 08:03
  • Well, the problem is at the time you posted your answer you didn't know that either ;) As he just added some information afterwards. Without enough context, the advice could have been wrong :) – ereOn Sep 05 '11 at 19:40
  • @ereOn: no, no, no, I didn't know that, but I did know if he had _no_ common interface, he did have a modeling problem. – xtofl Sep 06 '11 at 06:51
0

There are several ways to do this:

  1. Using the more generic base type, if there is an inheritance relationship.
  2. Using void* and explicitly casting where appropriate.
  3. Creating a wrapper class with the inheritance relationship needed for #1.
  4. Using a discriminating container via union.

Since others have already described the first three options, I will describe the fourth. Basically, a discriminated container uses a union type to use the storage of a single object for storing one of multiple different values. Typically such a union is stored in a struct along with an enum or integral type for distinguishing which value is currently held in the union type. As an example:

// Declarations ...
class FirstType;
class SecondType;

union PointerToFirstOrSecond {
   FirstType* firstptr;
   SecondType* secondptr;
};

enum FIRST_OR_SECOND_TYPE {
   FIRST_TYPE,
   SECOND_TYPE
};

struct PointerToFirstOrSecondContainer {
   PointerToFirstOrSecond pointer;
   FIRST_OR_SECOND_TYPE which;
};

// Example usage...

void OperateOnPointer(PointerToFirstOrSecondContainer container) {
    if (container.which == FIRST_TYPE) {
       DoSomethingWith(container.pointer.firstptr);
    } else {
       DoSomethingElseWith(container.pointer.secondptr);
    }
}

Note that in the code below, "firstptr" and "secondptr" are actually two different views of the same variable (i.e. the same memory location), because unions share space for their content.

Note that even though this is a possible solution, I seriously wouldn't recommend it. This kind of thing isn't very maintainable. I strongly recommend using inheritance for this if at all possible.

Michael Aaron Safyan
  • 93,612
  • 16
  • 138
  • 200