Безопасно ли приводить указатель на объявленный заранее класс к его истинному базовому классу в C ++? - PullRequest
3 голосов
/ 05 мая 2010

В одном заголовочном файле у меня есть:

#include "BaseClass.h"
// a forward declaration of DerivedClass, which extends class BaseClass.
class DerivedClass ;
class Foo {
   DerivedClass *derived ;
   void someMethod() {
       // this is the cast I'm worried about.
       ((BaseClass*)derived)->baseClassMethod() ;
   } 
};

Теперь DerivedClass (в своем собственном заголовочном файле) является производным от BaseClass, но компилятор не знает, что во время чтения приведенного выше определения для класса Foo. Однако Foo ссылается на указатели DerivedClass, а DerivedClass ссылается на указатели Foo, поэтому они не могут оба знать объявление друг друга.

Первый вопрос заключается в том, безопасно ли (в соответствии со спецификацией C ++, а не в каком-либо конкретном компиляторе) приводить указатель производного класса к типу указателя на базовый класс в отсутствие полного определения производного класса.

Второй вопрос: есть ли лучший подход? Я знаю, что мог бы вывести тело someMethod () из определения класса, но в этом случае важно, чтобы оно было встроено (часть реальной, измеренной точки доступа - я не догадываюсь).

Ответы [ 3 ]

3 голосов
/ 05 мая 2010

Может работать, но риск огромен.

Проблема в том, что в большинстве случаев Derived* и Base* действительно будут иметь одинаковое значение под капотом (которое вы можете напечатать). Однако это не так, как только у вас есть виртуальное наследование и множественное наследование, и, конечно, стандарт не гарантируется.

При использовании static_cast компилятор выполняет необходимую арифметику (так как ему известно расположение класса) для настройки значения указателя. Но эта корректировка не выполняется reinterpret_cast или приведением в стиле C.

Теперь вы можете прекрасно переделать свой код следующим образом:

// foo.h
class Derived;

class Foo
{
public:
  void someMethod();
private:
  Derived* mDerived;
};

// foo.cpp
#include "myProject/foo.h"

#include "myProject/foo.cpp"

void Foo::someMethod() { mDerived->baseClassMethod(); }

В любом случае: вы используете указатель на Derived, хотя все в порядке, есть ряд ошибок, о которых вы должны знать: особенно, если вы намереваетесь new экземпляр класса, вам необходимо переопределите Copy Constructor, Assignment Operator и Destructor класса, чтобы правильно обработать указатель. Также не забудьте инициализировать значение указателя в каждом Constructor (либо NULL, либо экземпляру Derived).

Это не невозможно, но документируйте себя.

3 голосов
/ 05 мая 2010

Вы должны использовать приведения в стиле c ++, а не в стиле c.Причина в том, что то, что вы делаете, на самом деле является reinterpret_cast, который, за исключением нескольких редких случаев, практически небезопасен.

Выполнение, которое вы выполняете, выполняется неправильно.Возможно, вы сможете безопасно выполнять приведение между этими двумя значениями, это определено реализацией (согласно стандарту гарантируется, что только void * может содержать указатели на любой тип), но если вы впоследствии действительно используете новый указатель, вы вызовете носовых демонов.

Большую часть времени эти носовые демоны доброкачественны, и вы их не заметите (при достаточном количестве реализаций).Однако, если вы используете множественное наследование, эти же самые носовые демоны станут довольно хитрыми.Тем не менее, вы никогда не должны принимать неопределенное поведение как «безопасное» или что-либо иное, чем изначально плохое, если просто нет другого способа получить то, что вам нужно (как профессионал, с которым я никогда не сталкивался).

Чтоприводит всю причину НЕ использовать броски в стиле c.Есть некоторые очень, очень редкие и очень редкие состояния, когда вам приходится это делать, но как профессионал я никогда не сталкивался с ними.Проблема с приведением в стиле c заключается в том, что они будут молча менять тип броска в зависимости от семантики ситуации.Незначительные изменения кода в отдельных областях кода могут серьезно изменить операцию приведения без вашего ведома.Приведение в стиле c ++ сообщит вам, когда семантика изменилась, требуя другого типа приведения, приведение в стиле c просто молча изменится;при использовании приведения в стиле c вы можете перейти от определенного к неопределенному поведению без какой-либо подсказки, что это произошло.

2 голосов
/ 05 мая 2010

Здесь у вас есть reinterpret_cast в терминах C ++ (поскольку «заведомо безопасное» static_cast приведет к ошибке компилятора), что я бы очень подозрительно.

Разве нельзя было бы вместо этого перенаправить объявление Foo для Derived?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...