dpwr
dpwr

Reputation: 2812

Best way to store/return inherited classes in c++

I'm writing a wrapper around some autogenerated classes to provide a better interface. I've written a simplified example to demonstrate the problem.

I have a class A which has a pointer to an object of class X, then I have class B (inherits A) which has a pointer to an object of class Y (inherits X).

That's all fine, except that I'm unsure what the best way is to store the pointers to X and Y. I need to be able to manipulate collections of objects of class A and B as if they were all A (with pointers to X being all I'd need there), which works fine with my example below.

My actual question is how best to handle the case of class B which needs to manipulate class Y objects rather than class X. I'm sure there is something quite obvious that I'm missing which I could use here. It feels clumsy to have to repeatedly cast this variable every time I use it and define a method with a different name (e.g. getY) for each class that inherits A that I create. There might be many methods like doStuff all of which would have to be calling getY.

class X {
private:
    int i;
public:
    X(int i) : i(i) {}
    int getI() {
        return i;
    }
};

class Y : public X {
private:
    int j;
public:
    Y(int i, int j) : X(i), j(j) {}
    int getJ() {
        return j;
    }
};

class A {
protected:
    X* x;
public:
    A(X* a) : x(a) {}
    X* get() {
        return x;
    }
};

class B : public A {
public:
    B(Y* y) : A(y) {}
private:
    //I could create a new function for each class like class B
    Y* getY() {
        return (Y*)x;
    }
public:
    void doStuff() {
        Y* y = getY();
        y->getJ();
        std::cout << "J: " << y->getJ() << cout::endl;
    }
};

Extract from main:

X* x = new X(5);
Y* y = new Y(5,6);
B b(y);
b.doStuff();

One alternative which occurs to me is that when I construct class B I could initialise a pointer of type Y variable pointing at the same location as the one which be set in X. As long as my pointers were const I think that should be safe from the two references ever pointing to different things.

Any ideas most welcome.

Thanks.

Upvotes: 2

Views: 168

Answers (2)

user420442
user420442

Reputation:

As long as Y derives from X, and B derives from A, then this is easy.

Virtual functions can differ by return type as long as those return types are polymorphic. Implement the following in A:

virtual X* Get() const { return x; }

And the following in B:

virtual Y* Get() const { return static_cast< Y* >( A::Get() ); }

Upvotes: 0

Peter Ruderman
Peter Ruderman

Reputation: 12485

Well, the first thing I would do is break the inheritance relationship between A and B. You say they have a common interface, so start by defining this interface:

class ILibraryWrapper {
public:
    virtual X * get() = 0;
};

I assume that A and B share a lot of code (hence the inheritence relationship), so next define a template class to implement the interface and define the common bits:

template <class T> class LibraryWrapper : public ILibraryWrapper {
protected:
    T * pointer;

public:
    LibraryWrapper( T * value ) : pointer( value ) {}

    virtual X * get() override { return static_cast<X *>( this->pointer ); }
};

Finally, define A and B like so:

class A : public LibraryWrapper<X> {
public:
    A( X * value ) : LibraryWrapper( value ) {}
};

class B : public LibraryWrapper<Y> {
public:
    B( Y * value ) : LibraryWrapper( value ) {}

    void doStuff() {
        std::cout << "J: " << this->pointer->getJ() << cout::endl;
    }
};

Virtually all your code should then deal with ILibraryWrapper pointers or references. If some code absolutely must know the concrete type of the wrapped object, then you can do a dynamic cast:

auto wrapper = dynamic_cast<LibraryWrapper<Y> *>( object );
if( wrapper != nullptr ) {
    // ...
}

But generally speaking, it's better to provide all needed functionality in the ILibraryWrapper interface.

Upvotes: 1

Related Questions