Reputation: 2812
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
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
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