Ну, я недавно столкнулся с делом, где меня сбили, отвлекаясь следующим образом:
class Derived: public Base {
public:
PyObject *GetPyObj() { return m_obj; }
void SetPyObj(PyObject *obj) { m_obj = obj }
private:
PyObject *m_obj;
};
Этот класс добавляет дополнительный m_obj
К сожалению, я приписал атрибут базовому классу где-то в моем коде совершенно небезопасным приведением типа:
Derived *node = <Derived *>some_ObjectPtr;
// Where "some_ObjectPtr" had been created as a "Base*" in the first place.
Что иногда приводило к произвольному поведению, когда я звонил SetPyObj()
, Благодаря Valgrind
Я смог определить, что я «писал за пределами моего объекта», и найти проблему:
...
==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)
...
Моя первая попытка была сдвинуть m_obj
в базовый класс как void*
и преобразовать SetPyObj()
в это:
void SetPyObj(PyObject *obj) {
this->SetObj(obj);
}
где Base::SetObj(obj)
выглядит так:
void SetObj(void *obj);
(Я выбрал void *
, потому что по ряду веских причин тип PyObject не был доступен в определении Base …)
Таким образом я писал в родительский класс, и проблема исчезла, но все же …
Мне не по себе от такого рода манипуляций, и я не очень доволен обходным решением (это не похоже на хорошую практику, я чувствую запах!).
Итак, вот мой вопрос:
Допустимо ли делать то, что я делал, пока я Base
классом только с дополнительными методами (без дополнительных переменных-членов)?
Если нет, то приемлемо ли в любом случае опускаться в таком контексте? В каких случаях приемлемо снижение рейтинга (я имею в виду, не создавая some_ObjectPtr
как Derived*
на первом месте)?
Если да, то какой был бы элегантный способ справиться с этим делом?
Примечание: использование таких функций, как dynamic_cast<>
не было выбора в моей среде (недоступно в Cython), и я не уверен, что это все равно помогло бы.
Дополнительный дополнительный вопрос: не могли бы вы объяснить, как обрабатывается размер в памяти данного экземпляра класса? Я ясно вижу, что в моем примере дополнительный атрибут переполняется его размером (64 бита), но добавление метода кажется безопасным (означает ли это, что если производный класс содержит только дополнительные методы, то и классы Derived и Base будут создавать объекты точно такой же размер?).
Я надеюсь, что я ясно выразил свои опасения.
Можно безопасно уменьшить указатель объекта на Derived*
только если этот объект был создан как Derived
или любой класс, унаследованный от Derived
, В других случаях это не может быть безопасно. Это может работать для некоторых реализаций, но это не разрешено языком.
(Я выбрал void *, потому что по ряду веских причин тип PyObject
не было доступно из базового определения …)
Это не веская причина. Вы никогда не должны использовать void*
в C ++. Вы должны поместить предварительное объявление PyObject в верхней части вашего заголовка. Учитывая реальное объявление PyObject, оператор предварительного объявления должен выглядеть следующим образом:
typedef struct _object PyObject;
Других решений пока нет …