2

I'm looking to do something only if the class is a specific derived class. That is I have:

class X{
    int id;
}

class A: public X{
    void run();
}

class B: public X{
    int lala;
}

And I want to do something along the line of:

main(){
    vector<X *> types;
    types.push_back(new A);
    types.push_back(new B);
    int var = 0;
    for(int i = 0; i<types.size(); i++){
        if(types[i].isType(A)) {types[i].run();} 
    }
    for(int i = 0; i<types.size(); i++){
        if(types[i].isType(B)) {var = lala;} 
    }
}

I do not want class B to have anything equivalent to run(), nor do I want class A to have an equivalent to lala.

I know fortran has a workaround with

select type ( x => var )
class is ( A )
    x.run()
end select

But I wasn't sure what my options in C++ were.

Thanks

Dave Lass
  • 115
  • 1
  • 8

4 Answers4

5

You are looking for dynamic_cast.

#include <vector>
using namespace std;

class X {
public:
    int id;
    virtual ~X() = default;
};

class A : public X {
public:
    void run() {}
};

class B : public X {
public:
    int lala;
};

main(){
    vector<X *> types;
    types.push_back(new A);
    types.push_back(new B);
    int var = 0;
    for(int i = 0; i<types.size(); i++){
        if (auto ta = dynamic_cast<A *>(types[i])) {
            ta->run();
        }
    }
    for(int i = 0; i<types.size(); i++){
        if (auto tb = dynamic_cast<B *>(types[i])) {
            var = tb->lala;
        }
    }
}

Also see it in action here: https://onlinegdb.com/B1d29P5if.

I had to fix a few other problems with the code. Since they are not a part of your question, I won't clarify here, but you are welcome to ask if something is not clear.

EDIT: The above solution has memory leaks, which I didn't fix, as it wasn't required by the question. For completeness, here is the main function with memory leaks fixed (https://onlinegdb.com/ByeOmu9iz):

int main() {
    vector<unique_ptr<X>> types;
    types.emplace_back(new A);
    types.emplace_back(new B);
    int var = 0;
    for(int i = 0; i < types.size(); ++i) {
        if (auto ta = dynamic_cast<A *>(types[i].get())) {
            ta->run();
        }
    }
    for(int i = 0; i < types.size(); ++i) {
        if (auto tb = dynamic_cast<B *>(types[i].get())) {
            var = tb->lala;
        }
    }
}

Note that this is a C++11 solution.

If you're working with an even older compiler, you'll have to keep using plain pointers as in the original solution, and deallocate the memory manually at the end by calling delete on each element of the vector. (And hope nothing throws an exception before you reach that step.) You'll also have to replace auto ta with A* ta and auto tb with B* tb.

gflegar
  • 1,583
  • 6
  • 22
  • 2
    It wouldn't have hurt to fix the leaks. – Christian Hackl Apr 10 '18 at 17:08
  • Thanks Goran. I guess a minor further question would be if A and B have derived classes, would the same if statement work on the derived classes. That is if C is derived from A and we dynamic cast a type of C with the "if (auto ta = dynamic_cast(types[i]))" would that return true? – Dave Lass Apr 10 '18 at 17:11
  • @ChristianHackl I know, I wanted to change the minimal amount of codo to make it compile - the question wasn't about the leaks :) But I'll add the code with fixed leaks for reference. – gflegar Apr 10 '18 at 17:12
  • @DaveLass yes, it also works if `C` is derived from `A`, and it will go through the path for `A`. Note that this is not true for @Rabster's solution. – gflegar Apr 10 '18 at 17:29
  • I also want to note that overuse of `dynamic_cast` is often a sign of bad code design. I don't know your exact application, but if you are trying to perform an operation on a collection of objects, which depends on the runtime type of the object, maybe you are looking for the visitor design pattern: https://en.wikipedia.org/wiki/Visitor_pattern – gflegar Apr 10 '18 at 17:41
4

A modern C++17 solution to this problem is to use a vector of variants, i.e. std::vector<std::variant<A, B>>. You need a modern compiler for this.

Here is a complete example, based on the std::variant documentation:

#include <vector>
#include <variant>
#include <iostream>

class X {
    int id;
};

class A: public X {
public:
    void run() {
        std::cout << "run\n"; // just for demonstration purposes
    }
};

class B: public X {
public:
    B(int lala) : lala(lala) {} // just for demonstration purposes
    int lala;
};

int main() {
    std::vector<std::variant<A, B>> types;

    types.push_back(A()); // no more new!
    types.push_back(B(123)); // no more new!

    int var = 0;

    for (auto&& type : types) {
        std::visit([&](auto&& arg) {
            using T = std::decay_t<decltype(arg)>;
            if constexpr (std::is_same_v<T, A>) {
                arg.run();
            } else {
                var = arg.lala;
            }
        }, type);
    }

    std::cout << var << '\n'; // just for demonstration purposes
}

As a nice bonus, this solution elegantly gets rid of dynamic allocation (no more memory leaks, no smart pointers necessary).

Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
  • 1
    This is a good solution for this problem (and performs better than the `dynamic_cast` variant), but I just wanted to mention that it wouldn't work anymore if we add new classes to the hierarchy, and want to also add them in `types`. Unless we change the signature of the vector. – gflegar Apr 10 '18 at 17:11
0

You could use dynamic_cast to check if the base class pointer is convertible to a derived instance.

Another option would be to have a virtual function that returns the typeinfo of the class and thus use that information to cast the pointer to a convertible type. Depending on how dynamic_cast is implemented this could be more performant. Thus, you could use this if you want to try and see whether or not this method is quicker on your platform.

As @Jarod42 noted, you would need to have a virtual function, destructor in this case, for dynamic_cast to work. In addition, you would simply need a virtual destrctor to avoid undefined behavior when deleting the instance.

Example

#include <iostream>
#include <string>
#include <vector>
#include <typeinfo>

struct A {
    virtual ~A() {

    }

    virtual const std::type_info& getTypeInfo() const {
        return typeid(A);
    }
};

struct B : public A {
    virtual const std::type_info& getTypeInfo() const override {
        return typeid(B);
    }
};

struct C : public A {
    virtual const std::type_info& getTypeInfo() const override {
        return typeid(C);
    }
};



int main()
{
    std::vector<A*> data;
    data.push_back(new A);
    data.push_back(new B);
    data.push_back(new C);

    for (auto& val : data) {
        if (val->getTypeInfo() == typeid(A)) {
            std::cout << "A";
        }
        else if (val->getTypeInfo() == typeid(B)) {
            std::cout << "B";
        }
        else if (val->getTypeInfo() == typeid(C)) {
            std::cout << "C";
        }
        std::cout << std::endl;
    }

    for (auto& val : data) {
        delete val;
    }
}
Rabster
  • 933
  • 1
  • 7
  • 11
  • 2
    `dynamic_cast` requires at least one virtual method (as destructor). – Jarod42 Apr 10 '18 at 16:42
  • 2
    *"This is generally more performant than `dynamic_cast`"* - Why should the compiler implement something which does exactly the same thing but be slower? Cite a source, please. – Christian Hackl Apr 10 '18 at 16:52
  • The definition of `getTypeInfo` is likely gonna be copy-pasted all over the place. One has to be very very careful here. – StoryTeller - Unslander Monica Apr 10 '18 at 16:53
  • You would stil have to cast it somehow to access it's members. You can use `static_cast` which doesn't introduce any overhead, unless you need to cast sideways due to multiple inheritance. Then you would have to use `dynamic_cast` anyway.This also answers @ChristianHackl's question - the compiler has to do something smarter for `dynamic_cast` to support casting sideways. – gflegar Apr 10 '18 at 17:06
  • I should probably try and avoid making such generalizations :P – Rabster Apr 10 '18 at 17:32
0

I have two ideas....

Why not have a shared method that returns a value that gives context as to whether or not it is an A or B? If for example, lala is expected to return only values 0 or greater, you could have void run() instead be int run() and return -1 at all times.

class X {
   int id;
   virtual int run() = 0; //Assuming X isn't meant to be instantiated
}

class A: public X {
   // Return -1 to differentiate between As and Bs
   int run() { return -1; }
}

class B: public X {
   int lala;
   int run() { return lala;}
}

Then you have...

main(){
vector<X *> types;
types.push_back(new A);
types.push_back(new B);
int var = 0, temp = 0;

for( int i = 0; i<types.size(); i++ ) {
    if( (temp = types[i].run()) != -1 )
        var = temp;
        ....
    }
}

Again, only works if lala would never expect to return a particular range of values.

You could also hide information in X, upon creation of an A or B to keep track of what you have.

class X {
    int id;
    bool isA;
}

class A: public X {
    A() : isA(true) { };
    void run();
}

class B: public X {
    B() : isA(false) { } ;
    int lala;
}

Then you have...

main(){
vector<X *> types;
types.push_back(new A);
types.push_back(new B);
int var = 0;

for( int i = 0; i<types.size(); i++ ) {
    if( types[i].isA == true ) {
        types[i].run();
    }
    else {
        var = types[i].lala;
    }
}

Naturally if you expect to add C, D, E, .... it will no longer be worth it, but for only two derived classes it isn't all that bad.

I would justify this based on the fact that users are already going to have to peer into the derived classes to see why they behave so differently for being derived from the same class. I would actually look into whether or not it makes sense for A and B to derive from X based on their interface.

I also wouldn't recommend dynamic_cast(ing) without informing someone that it's one of the more dangerous casts to perform and typically not recommended.

JoeManiaci
  • 435
  • 3
  • 15