3

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
Gauthier Boaglio
  • 10,054
  • 5
  • 48
  • 85

1 Answers1

1

It's safe to downcast an object pointer to Derived* only if this object has been created as Derived or any class inherited from Derived. In other cases it can't be safe. It may work for some implementations, but it is not allowed by the language.

(I chose void *, because, for some good reasons, the type PyObject wasn't available from the Base definition...)

It's not a good reason. You should never use void* in C++. You should place a forward declaration of PyObject in top of your header. Considering real declaration of PyObject, forward declaration statement should look like the following:

typedef struct _object PyObject;
Pavel Strakhov
  • 39,123
  • 5
  • 88
  • 127
  • All right for the downcast part, but regarding th void* thing, Base is part of an independent shared library which I want to keep independent from any Python specific stuffs... This is a good reason. Why not void* in C++ ? Is that also discouraged by the language ? – Gauthier Boaglio Jun 09 '13 at 08:40
  • 1
    `void*` is used in C because it's the only way to do several things. C++ provides better ways to do that things, there is no need in `void*` in C++. It also provides no compile-time checking, so it's much better to use C++ ways. In your particular case, I would create some class named `MyObject` and use `Base::setObj(MyObject*)`. For `Derived` class I would create another class `MyPyObject` that extends `MyObject` and hold `PyObject*` pointer. It will ensure that you pass only correct objects to `Base::setObj`, it will protect you against stupid mistakes like passing `int*` to setObj. – Pavel Strakhov Jun 09 '13 at 08:52
  • All right, that makes sense, explained that way. Thanks for the advices. And, in a more general consideration, what would be the C++ way, if I do want to deal with really undefined type reference. Dealing with a user defined object storage / reference, by example ? If I want him (the user) to be allowed to store an `int*` as well as any kind of object he created himself ? Should I provide a base dummy object, and `force` him to derivate from it instead ? Pardon me if I'm a bit C++ dumb... – Gauthier Boaglio Jun 09 '13 at 09:08
  • 1
    I think templates are the best option. For example, `std::vector` works with any user defined class `T`. – Pavel Strakhov Jun 09 '13 at 09:16
  • That makes sense too. Templates are not that easy to use with `Cython` (only partially supported) but it seems doable to me. – Gauthier Boaglio Jun 09 '13 at 09:23