Dave Lass
Dave Lass

Reputation: 135

Obtain variable from derived class c++

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

Upvotes: 2

Views: 114

Answers (4)

Rabster
Rabster

Reputation: 1013

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;
    }
}

Upvotes: 0

gflegar
gflegar

Reputation: 1613

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.

Upvotes: 5

JoeManiaci
JoeManiaci

Reputation: 455

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.

Upvotes: 0

Christian Hackl
Christian Hackl

Reputation: 27518

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).

Upvotes: 4

Related Questions