Действительно ли dynamic_cast работает для множественного наследования? - PullRequest
6 голосов
/ 30 августа 2011

Я хотел посмотреть, возможно ли создать «интерфейсы», наследовать их, а затем проверить во время выполнения, реализует ли этот интерфейс какой-либо случайный класс. Вот что у меня есть:

struct GameObject {
    int x,y;
    std::string name;

    virtual void blah() { };
};

struct Airholder {
   int oxygen;
   int nitrogen;
};

struct Turf : public GameObject, public Airholder {
   Turf() : GameObject() {
      name = "Turf";
   }

   void blah() { };
};

void remove_air(GameObject* o) {
   Airholder* a = dynamic_cast<Airholder*>(o);
   if(!a) return;
   a->oxygen   = 0;
   a->nitrogen = 0;
};

Теперь это работает. В документации сказано, что это работает, тестовый пример работает. Но он также не компилировался, пока я не добавил виртуальный метод в GameObject. Дело в том, что я действительно не знаю, предназначена ли эта функция для такого использования. Меня удивило то, что мне нужно объявить виртуальную функцию для класса, который я проверяю. Но, очевидно, его нет, сам класс, который я проверяю, не имеет виртуальных функций, фактически весь мой код не имеет ничего общего с виртуальными функциями, это совершенно другой подход.

Итак, я предполагаю, что мой вопрос: если то, что я делаю, действительно работает, зачем мне виртуальная функция, чтобы дать моему классу vtable? Почему я не могу объявить класс "типом времени выполнения" или чем-то другим без виртуальных функций?

Ответы [ 6 ]

6 голосов
/ 30 августа 2011

§ 5.2.7 стандарта гласит:

  1. Результат выражения dynamic_cast (v) является результатом преобразования выражения v в тип T. T должен быть указателем или ссылкой на полный тип класса или «указателем на cv void». Типы не должны быть определяется в dynamic_cast. Оператор dynamic_cast не должен отбрасывать константу (5.2.11).
  2. Если T является типом указателя, v должно быть r-значением указателя на завершенный тип класса, а результатом является r-значение тип T. Если T является ссылочным типом, v должен быть lvalue полного типа класса, а результатом является lvalue тип, на который ссылается Т.
  3. Если тип v совпадает с требуемым типом результата (который для удобства в этом случае будет называться R описание), или он такой же, как R, за исключением того, что тип объекта класса в R более квалифицирован cv, чем класс тип объекта в v, результат v (при необходимости преобразуется).
  4. Если значение v является нулевым значением указателя в случае указателя, результатом является нулевое значение указателя типа R.
  5. Если T - это «указатель на cv1 B», а v имеет тип «указатель на cv2 D», так что B является базовым классом D, результатом является указатель на уникальный подобъект B объекта D, на который указывает v. Аналогично, если T является «ссылкой на cv1 B» и v имеет тип «cv2 D», так что B является базовым классом D, результатом является l-значение для уникального 60) B подобъекта объекта D, на который ссылается v. И в указателе, и в ссылочных случаях cv1 должен быть одинаковым или cv-квалификация больше, чем cv2, и B должны быть доступным однозначным базовым классом Д. [Пример:

    struct B {};
    структура D: B {};
    void foo (D * dp)
    {
    B * bp = dynamic_cast (dp); // эквивалентно B * bp = dp;
    }
    - конец примера]

  6. В противном случае v должен быть указателем или значением l полиморфного типа (10.3).

И , чтобы сделать тип полиморфным, ему нужна виртуальная функция , как указано в § 10.3:

Виртуальные функции поддерживают динамическое связывание и объектно-ориентированное программирование. Класс, который объявляет или Унаследованная виртуальная функция называется полиморфным классом.

Итак, причина в том, что "потому что стандарт говорит так". Это на самом деле не говорит вам , почему стандарт говорит так, хотя, но другие ответы покрывают это хорошо, я думаю.

1 голос
/ 30 августа 2011

Итак, я предполагаю, что мой вопрос: если то, что я делаю, действительно работает, зачем мне виртуальная функция, чтобы дать моему классу vtable? Почему я не могу объявить класс "типом времени выполнения" или чем-то другим без виртуальных функций?

Наличие виртуальной функции - это то, что делает класс полиморфным в C ++. dynamic_cast<> работает только с полиморфными классами. (Компилятор отклонит динамическое приведение для неполиморфного объекта.)

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

Подобные понятия существуют в других местах языка. Основополагающий принцип заключается в том, что если вы не хотите использовать какую-либо функцию с высоким уровнем полезности, вам не нужно платить за тот факт, что некоторые люди хотят ее использовать.

0 голосов
/ 30 августа 2011

Как уже говорили другие, вам нужна как минимум одна виртуальная функция, чтобы сделать класс полиморфным. Почему это важно, так это то, что dynamic_cast сам по себе является полиморфной операцией! При наличии указателя на базовый класс он возвращает разные результаты в зависимости от того, к какому объекту он вызывается.

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

0 голосов
/ 30 августа 2011

Есть две основные причины.Во-первых, для этого просто нет смысла.Точка наследования - это виртуальные функции.Если вы не используете виртуальные функции, не используйте наследование.

Во-вторых, очень сложно реализовать dynamic_cast, который работает без виртуальных функций из-за модели компиляции C ++.Единственный способ реально реализовать dynamic_cast - это работать с виртуальной таблицей - двоичный блок данных не содержит типов.Вы можете определить класс и затем только dynamic_cast его в одном TU - теперь один TU думает, что у класса есть vtable, а другой - нет.Это было бы плохо сейчас.Разрешение dynamic_cast на классы, которые еще не имеют виртуальных функций, будет export, что означает «Чрезвычайно сложно реализовать».

0 голосов
/ 30 августа 2011

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

0 голосов
/ 30 августа 2011

[РЕДАКТИРОВАТЬ] Согласно комментариям (люди намного умнее меня) мой ответ совершенно неверный.Тем не менее, сделайте ваши деструкторы виртуальными в любом случае.[/ EDIT]

В C ++ я считаю, что преобразование в базовый тип безопасно только в том случае, если деструктор является виртуальным.Технически это безопасно, но в действительности вам почти всегда нужен виртуальный деструктор.Например:

class Base {
   int thingy;
};
class Derived : Base{
   int *array;
   Derived() {array = new int[100];}
   ~Derived() {delete [] array;}
};
int main() {
    std::auto_ptr<Base> obj(dynamic_cast<Base*>(new Derived));
}

В этом примере, когда obj выходит из области видимости, auto_ptr автоматически вызывает деструктор базы , но не вызывает производный деконструктор , поскольку тип является базой, не производное.[Редактировать: исправления] Это вызывает неопределенное поведение (в лучшем случае, это вызывает утечку памяти).Я понятия не имею, почему C ++ не требует виртуального деструктора для компиляции приведений, это действительно должно.

...