OOP question: Is upcasting ok in this situation?

J

Juha Nieminen

If I'm not mistaken, general OOP wisdom says that upcasting should
usually be avoided if possible. I have a situation, however, where I
can't think of a better way than upcasting for this specific solution.
I was wondering if there exists a "standard" solution to this which
avoids the upcasting and does it more cleanly.

Assume that we have, for example, some kind of container/editing class
named PrimitiveEditor which handles objects of (the abstract) type
Primitive. So you can have, for example, classes like:

class Circle: public Primitive { ... };
class Square: public Primitive { ... };
etc.

The PrimitiveEditor defines a callback interface, let's call it
PrimitiveHandlingCallback, and it's used, for example, like this:

class MyClass: public PrimitiveHandlingCallback
{
PrimitiveEditor editor;
std::list<Circle> circles;
std::list<Square> squares;

public:
MyClass(): editor(this) {}

// Derived from PrimitiveHandlingCallback:
virtual void doSomethingToPrimitive(Primitive*);
};

Now, each time MyClass creates an instance of Circle or Square (into
the correspondent list) it gives a pointer to that instance to the
editor. The editor is called to handle those objects. The editor may
call the doSomethingToPrimitive() function so that MyClass can handle
the primitives in whatever special way it needs to.

And this is where my question raises. Is it ok to do it like this:

void MyClass::doSomethingToPrimitive(Primitive* p)
{
Circle* circle = dynamic_cast<Circle*>(p);
if(circle)
{
// Do something with circle, specific to the type Circle
return;
}

Square* square = dynamic_cast<Square*>(p);
if(square)
{
// Do something with square, specific to the type Square
return;
}
}

Is there any better/cleaner way of doing this same thing without
having to upcast?
 
A

Alf P. Steinbach

* Juha Nieminen:
If I'm not mistaken, general OOP wisdom says that upcasting should
usually be avoided if possible.

I think you mean downcasting, not upcasting.

I have a situation, however, where I
can't think of a better way than upcasting for this specific solution.
I was wondering if there exists a "standard" solution to this which
avoids the upcasting and does it more cleanly.

Assume that we have, for example, some kind of container/editing class
named PrimitiveEditor which handles objects of (the abstract) type
Primitive. So you can have, for example, classes like:

class Circle: public Primitive { ... };
class Square: public Primitive { ... };
etc.

The PrimitiveEditor defines a callback interface, let's call it
PrimitiveHandlingCallback, and it's used, for example, like this:

class MyClass: public PrimitiveHandlingCallback
{
PrimitiveEditor editor;
std::list<Circle> circles;
std::list<Square> squares;

public:
MyClass(): editor(this) {}

// Derived from PrimitiveHandlingCallback:
virtual void doSomethingToPrimitive(Primitive*);

Prefer passing argument by reference, unless you specifically want to
support nullpointers.
};


Now, each time MyClass creates an instance of Circle or Square (into
the correspondent list) it gives a pointer to that instance to the
editor.

Why not a reference?

The editor is called to handle those objects. The editor may
call the doSomethingToPrimitive() function so that MyClass can handle
the primitives in whatever special way it needs to.

And this is where my question raises. Is it ok to do it like this:

void MyClass::doSomethingToPrimitive(Primitive* p)
{
Circle* circle = dynamic_cast<Circle*>(p);
if(circle)
{
// Do something with circle, specific to the type Circle
return;
}

Square* square = dynamic_cast<Square*>(p);
if(square)
{
// Do something with square, specific to the type Square
return;
}
}

Is there any better/cleaner way of doing this same thing without
having to upcast?

Your example of PrimitiveHandlingCallback and PrimitiveEditor is quite
confusing.

But essentially it seems your question reduces to how to do
type-specific things to objects in an heterogenous container.

In the simplest case, some common type for those objects will simply
provide a virtual function:

struct ContainedObject
{
virtual ~ContainedObject() {}
virtual void someOperation() {}
};

One level up in complexity, it may be that someOperation() will only be
meaningful for a subset of the object types. And then it's an
engineering decision whether to provide this operation in
ContainedObject, with failure for objects that don't support it, or
whether to e.g. signal presence of that functionality via an interface.
The interface way might look like

struct ISomeOperation
{
virtual ~ISomeOperation() {}
virtual void someOperation() = 0;
};

struct ContainedObject
{
virtual ~ContainedObject() {}
};

struct Square: ContainedObject, virtual ISomeOperation
{
virtual void someOperation() {}
};

void foo( ContainedObject& o )
{
ISomeOperation* pInterface = dynamic_cast<ISomeOperation*>( &o );
if( pInterface != 0 ) { pInterface->someOperation(); }
}

Then further up in complexity it might be that the type-specific
operation to be performed uses knowledge that only resides in the
"calling" object, e.g. the container object.

In that case the downcasting can be centralized in someOperation, which
handed a general Container references calls back on type-specific member
functions:


struct Square;
struct Circle;
struct Container;

struct ContainedObject
{
virtual ~ContainedObject() {}
virtual void someOperation( Container& ) {}
};

struct Container
{
virtual void handle( Square& );
virtual void handle( Circle& );

void foo( ContainedObject& o )
{
o.someOperation( *this );
}
};

struct Square: ContainedObject
{
void someOperation( Container& c ) { c.handle( *this ); }
}

This "ask for callback" pattern is generally known as the visitor
pattern. Some people then prefer to use the name "visit" instead of
"someOperation", i.e., dear object, please visit me, passing your
esteemed self as argument.

Cheers, & hth.,

- Alf
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top