Well, I recently ran into the case where I downcasted, by distraction as follows:
class Derived: public Base {
public:
PyObject *GetPyObj() { return m_obj; }
void SetPyObj(PyObject *obj) { m_obj = obj }
private:
PyObject *m_obj;
};
This class adding an additional m_obj
attribute to the Base class, unfortunately I had, somewhere in my code a totally unsafe cast like this:
Derived *node = <Derived *>some_ObjectPtr;
// Where "some_ObjectPtr" had been created as a "Base*" in the first place.
Which was leading to some arbitrary behavior sometimes when I was calling SetPyObj()
. Thanks to Valgrind
, I've been able to identify that I was "writing beyond the end of my object" and to locate the problem:
...
==5679==
==5679== Invalid write of size 8
==5679== at 0x812DB01: elps::Derived::SetPyObj(_object*) (ALabNetBinding.cpp:27)
==5679== by 0x8113B2C: __pyx_f_5cyelp_12PyLabNetwork_Populate(__pyx_obj_5cyelp_PyLabNetwork*, int, int) (cyelp.cpp:10112)
...
My first attempt was to shift m_obj
to the Base class as a void*
, and convert the SetPyObj()
into this :
void SetPyObj(PyObject *obj) {
this->SetObj(obj);
}
where Base::SetObj(obj)
looks like this :
void SetObj(void *obj);
(I chose void *
, because, for some good reasons, the type PyObject wasn't available from the Base definition...)
This way I was writing into the parent class, and the problem disappeared, but still...
I am not at ease with this kind of manipulations and not really satisfied with the workaround (It doesn't look like a good practice, I can smell it!).
So, here's my question:
Is it "acceptable" to do what I did as long as I extend Base
by a class with additional methods only (no additional member variables)?
If not, is it acceptable to downcast in such a context anyway? Which are the cases where downcasting is acceptable (I mean other than having created some_ObjectPtr
as a Derived*
in the first place)?
If yes, what would have been an elegant way to deal with that case?
Note: using features like dynamic_cast<>
was not an option in my environment (not available with Cython) and I am not sure it would have helped anyway.
Bonus secondary question: Could you explain how the size in memory of a given instance of a class is handled? I can clearly see that, in my example, the additional attribute overflows by its size (64 bits), but adding a method seems safe (does it mean that if the derived class contains only additional methods both Derived and Base classes will produce objects of the exact same size?).
I hope I made my concerns clear.