I haven't really found anything on the topic other than its bad to cast, but I would assume there is a "proper" way to accomplish this.

To illustrate:

Class B and Class C both inherit from Class A.

I have a function MyFunction(A obj);

In MyFunction() I would check if the object passed is of type Class B or C.

I determine this and then do a static_cast, but it feels like this has been causing issues, and have read this is not a good Idea.

This concept is fairly simple and I would assume that there is a good way to go about doing this. Can anyone give me some insight on this subject? Thanks.

Recommended Answers

All 7 Replies

How about using virtual functions?

#include <iostream>

using namespace std;

struct A
{
    virtual void print_me() const {}

    virtual ~A() {}
};

struct B : A
{
    void print_me() const
    {
        cout << "Hello, I'm B!" << endl;
    }
};

struct C : A
{
    void print_me() const
    {
        cout << "Hi there, I'm C!" << endl;
    }
};

void print(const A & obj)
{
    obj.print_me();
}

int main()
{
    B b;
    C c;

    print(b);
    print(c);
}

This concept is fairly simple and I would assume that there is a good way to go about doing this.

Assuming polymorphism doesn't work for some reason (usually due to violation of the substitution principle) then dynamic_cast can be used to discover the type. However, if you find yourself using dynamic_cast, the general consensus is that your design is already flawed and should be fixed rather than worked around:

#include <iostream>

class A {
public:
    virtual ~A() = 0;
};

A::~A() {}

class B: public A {};
class C: public A {};
class D: public A {};

void MyFunction(const A& obj)
{
    if (dynamic_cast<const B*>(&obj) != 0)
        std::cout << "B object\n";
    else if (dynamic_cast<const C*>(&obj) != 0)
        std::cout << "C object\n";
    else
        std::cout << "Oh noes!\n";
}

int main()
{
    MyFunction(B());
    MyFunction(C());
    MyFunction(D());
}

If you dynamic_cast to a pointer type, an invalid destination type will result in a null pointer. In the above code I added a D class to highlight the biggest problem with this approach: you need a case for every derived class, and adding a new class means updating the casting code to handle it.

These are both valid, but I don't think I explained exactly what I was looking for opinions on.

So using the original example, B and C inherit from A. So B and C "share" for example 5 data members, but B has 3 extra, and C has 2 extra. I want to be able to pass B or C to a function with out having to overload it, so I create the function using a class A parameter. When I pass it through, depending if I pass either B or C(which I can determine with a flag), I want to modify a specific data member belonging to the passed object.

So in code this could look like

void SomeRandomClass::Function(A *object)
{
     //if i passed a B object
    if(object->type == 'b')
      //Modify data value specific to an object of type B
      //So here I would cast
      static_cast<B*>(object)->

     //If i passed a C object
    if(object->type == 'c')
      //Modify data value specific to an object of type C
      //So here I would cast
      static_cast<C*>(object)->
}

Thats not how I check, but the concept is what I am getting at. Is this just a bad design? and if so, how would I go about doing this a better way?

I don't think I explained exactly what I was looking for opinions on.

No, I think we got it. You're seeing the case I mentioned (violation of the substitution principle) that calls for dynamic_cast. You can use the pointer returned by dynamic_cast to modify the object:

#include <iostream>

class A {
public:
    virtual ~A() = 0;
};

A::~A() {}

class B: public A {
public:
    int x;
    B(int x): x(x) {}
};

class C: public A {
public:
    int y;
    C(int y): y(y) {}
};

void MyFunction(A& obj)
{
    if (B *b = dynamic_cast<B*>(&obj))
        b->x = 11;
    else if (C *c = dynamic_cast<C*>(&obj))
        c->y = 22;
}

int main()
{
    B b(123);
    C c(234);
    
    MyFunction(b); std::cout << b.x << '\n';
    MyFunction(c); std::cout << c.y << '\n';
}

Is this just a bad design?

Probably, yes.

how would I go about doing this a better way?

It depends on what these classes really represent and what they're designed to do. Using A, B, and C is fine for showing examples of techniques, but to suggest a better design in an actual program, we need something more concrete.

I'm curious as to why you can't use polymorphism like they suggested to have the object run its own specific function to do whats needed.

As for the static_cast vs dynamic_cast, the only difference in effect between the two is that the dynamic_cast does a run-time check to see if the cast is possible. In other words, if you pass a pointer to class A, and try to cast to a pointer to B, then dynamic_cast checks, at run-time, that the pointed-to object actually is of class B (or any class derived from B). If you have another mechanism to make sure that the pointed-to object can be cast to this other type, then you don't need the run-time check that dynamic_cast does and then, static_cast is sufficient, correct and more efficient. So, there is nothing wrong with your code in that regard, you don't need dynamic_cast since you can already determine the actual type via this "type" data member.

It is indeed bad design to require such a function. You should try to find some sort of abstract functionality that your function does, and try to turn it into a virtual function in your base class A, which you can override for the special or additional behaviour required in B and in C. You should give us more details about why you are writing this function and these classes, because we need to know the functionality you are trying to implement to be able to help you find a better alternative.

The reason this is bad design is because you break several tenants of OOP, that is, encapsulation is broken because an external function has to tweak data members of the classes, abstraction is broken because an external function has to deal with the details of the classes rather than an abstract interface to it, and scalability is broken because every time you add a derived class, you need to reimplement and add options to this external function.

"Bad design" does not mean that you are forbidden to use this. It just means you have to look hard for an alternative or better design before you decide to use this solution. But, sometimes, in the real world, there are no better alternatives, and, in fact, this pattern you are using is common enough to have a name, it's called the "Visitor Pattern", one of my least favorite design patterns in OOP, but one of the nicest patterns in generic programming. There are sometimes "necessary evils" in programming, especially with limited knowledge. But you have to make real sure that it is actually "necessary", tell us about your design (functionality you are trying to implement) and we can see, with you, if it is indeed a necessary pattern in your case.

I ended up implementing virtual functions. Ive never really used virtual before because I didnt fully understand them, but now that I have actually used it, I realize how powerful they are! Thanks for the suggestions!

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.