Почему C++ не позволяет Вам запрашивать указатель на большую часть производного класса?

(На этот вопрос нужно, вероятно, ответить со ссылкой на Stroustrup.)

Кажется чрезвычайно полезным смочь запросить указатель на большую часть производного класса, как в следующем:

class Base { ... };
class DerivedA { ... };
class DerivedB { ... };
class Processor
{
  public:
  void Do(Base* b) {...}
  void Do(DerivedA* d) {...}
  void Do(DerivedB* d) {...}
};

list things;
Processor p;
for(list::iterator i=things.begin(), e=things.end(); i!=e; ++i)
{
    p.Do(CAST_TO_MOST_DERIVED_CLASS(*i));
}

Но этот механизм не обеспечивается в C++. Почему?

Обновление, мотивируя пример:

Предположим вместо того, чтобы иметь Основу и Полученный и Процессор, Вы имеете:

class Fruit
class Apple : public Fruit
class Orange: public Fruit

class Eater
{
   void Eat(Fruit* f)  { ... }
   void Eat(Apple* f)  { Wash(f); ... }
   void Eat(Orange* f) { Peel(f); ... }
};

Eater me;
for each Fruit* f in Fruits
    me.Eat(f);

Но это хитро, чтобы сделать в C++, требуя интеллектуальных решений как шаблон "посетитель". Вопрос, затем: Почему это хитро, чтобы сделать в C++, когда что-то как "CAST_TO_MOST_DERIVED" сделало бы его намного более простым?

Обновление: Википедия знает все

Я думаю, что Понтский Gagge имеет хороший ответ. Добавьте к нему этот бит из статьи в Википедии о Нескольких Отправка:

"Stroustrup упоминает, что любил понятие Мультиметодов в Дизайне и Эволюции C++ и рассмотрел реализацию его в C++, но утверждает, что не мог найти эффективную демонстрационную реализацию (сопоставимой с виртуальными функциями) и разрешить некоторые возможные проблемы неоднозначности типа. Он продолжает заявлять, что, хотя функция все еще была бы хороша иметь, что она может быть приблизительно реализована с помощью дважды, диспетчеризируют, или основанная на типе справочная таблица, как обрисовано в общих чертах в примере C/C++ выше так является низкоприоритетной функцией будущих изменений языка".

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

5
задан Matthew Lowe 18 June 2010 в 15:44
поделиться

10 ответов

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

При этом что-то подобное может быть полезно для двойной отправки , особенно если у вас также есть иерархия процессоров . Но как это реализовать компилятору?

Во-первых, вам нужно извлечь то, что вы называете «наиболее перегруженным типом» во время выполнения. Это можно сделать, но как бы вы поступили, например, множественное наследование и шаблоны? Каждая функция языка должна хорошо взаимодействовать с другими функциями - а в C ++ есть множество функций!

Во-вторых, для того, чтобы ваш пример кода работал, вам необходимо получить правильную статическую перегрузку в зависимости от типа среды выполнения (что C ++ не допускает, поскольку он разработан).Хотели бы вы, чтобы это соответствовало правилам поиска во время компиляции , особенно с несколькими параметрами? Хотели бы вы, чтобы эта диспетчерская среда выполнения также учитывала тип среды выполнения вашей иерархии процессора и какие перегрузки они добавили? Какой объем логики вы хотите, чтобы компилятор автоматически добавлял в диспетчер среды выполнения? Как бы вы поступили с недопустимыми типами среды выполнения? Знают ли пользователи этой функции о стоимости и сложности того, что выглядит как простое приведение типов и вызов функции?

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

4
ответ дан 18 December 2019 в 05:26
поделиться

Во-первых, C ++ позволяет запрашивать указатель на наиболее производный класс в числовых терминах (т.е. только числовое значение адреса) . Это то, что делает dynamic_cast в void * .

Во-вторых, нет способа получить указатель на самый производный класс в термах точного типа самого производного класса. В C ++ приведение типов работает с статическими типами , а статическим типом является концепцией времени компиляции. Перегрузка функций на основе типов также является процессом во время компиляции. В вашем случае точный наиболее производный тип неизвестен во время компиляции, поэтому он не может быть приведен к нему и не может разрешить его перегрузку. Запрос на такое приведение не имеет смысла в области языка C ++.

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

8
ответ дан 18 December 2019 в 05:26
поделиться

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

6
ответ дан 18 December 2019 в 05:26
поделиться

Это невозможно в C ++, но то, что вы хотите достичь, легко сделать с помощью шаблона проектирования Посетитель :

class Base
{
    virtual void accept(BaseVisitor& visitor) { visitor.visit(this); }
};

class DerivedA
{
    virtual void accept(BaseVisitor& visitor) { visitor.visit(this); }
};

class DerivedB
{
    virtual void accept(BaseVisitor& visitor) { visitor.visit(this); }
};

class BaseVisitor
{   
    virtual void visit(Base* b) = 0;
    virtual void visit(DerivedA* d) = 0;
    virtual void visit(DerivedB* d) = 0;
};

class Processor : public BaseVisitor
{
    virtual void visit(Base* b) { ... }
    virtual void visit(DerivedA* d) { ... }
    virtual void visit(DerivedB* d) { ... }
};

list<Base*> things;
Processor p;
for(list<Base*>::iterator i=things.begin(), e=things.end(); i!=e; ++i)
{
    (*i)->visit(p);
}
1
ответ дан 18 December 2019 в 05:26
поделиться

C ++ интерпретирует данные в контексте связанного типа. Когда вы сохраняете экземпляр DerivedA * или DerivedB * в списке, этот ассоциированный тип обязательно должен быть Base *. Это означает, что сам компилятор больше не может определять, что это указатели на один из подклассов, а не на базовый класс. Хотя теоретически вы можете выполнить приведение к производному классу LESS, глядя на наследование связанного типа, информация, необходимая для того, чтобы делать то, что вы хотите, просто недоступна во время компиляции.

0
ответ дан 18 December 2019 в 05:26
поделиться

Наверное, потому, что виртуальные функции вместо этого делают за вас. Реализация виртуальной функции, ближайшей к наиболее производному классу, будет вызываться, когда вы вызываете ее через указатель или ссылку базового класса.

8
ответ дан 18 December 2019 в 05:26
поделиться

Вы ищете двойную диспетчеризацию. Это можно сделать в C++, как показано по этой ссылке, но это не очень красиво, и в основном включает использование двух виртуальных функций, вызывающих друг друга. Если вы не можете модифицировать некоторые объекты в вашем дереве наследования, вы также не сможете использовать эту технику.

2
ответ дан 18 December 2019 в 05:26
поделиться

В C++ разрешение перегрузки происходит во время компиляции. Ваш пример требует определения реального типа *i во время выполнения. Чтобы сделать это во время выполнения, потребовалась бы проверка типа во время выполнения, а поскольку C++ - язык, ориентированный на производительность, он целенаправленно избегает этих затрат. Если вы действительно хотите сделать это (и мне было бы интересно увидеть более реалистичный пример), вы могли бы динамически_кастировать в самый производный класс, затем, если это не удастся, во второй самый производный класс, и так далее, но это требует знания иерархии классов наперед. А знать всю иерархию заранее может быть невозможно - если класс DerivedB находится в публичном заголовке, возможно, другая библиотека использует его и создала еще более производный класс.

2
ответ дан 18 December 2019 в 05:26
поделиться

Почему у C++ нет этого? Возможно, создатели никогда не думали об этом. Или, возможно, они не считали его достаточно подходящим или полезным. Или, возможно, были проблемы с реальной попыткой сделать это в этом языке.

Что касается последней возможности, вот мысленный эксперимент:

Предположим, что эта функция существует для того, чтобы компилятор написал код, который исследует указанный динамический тип и вызывает соответствующую перегрузку. Теперь допустим, что в отдельной части кода есть class DerivedC : Base {...};. И допустим, что соответствующая перегрузка Processor::Do не добавлена.

Учитывая все это, что должна делать программа, когда она пытается выбрать соответствующую перегрузку? Это несоответствие не может быть поймано во время компиляции. Должна ли она попытаться подняться по иерархии классов, чтобы найти функцию, соответствующую базовому классу? Должна ли она выбросить специальное исключение? Должен ли он просто упасть? Есть ли какая-то другая возможность? Есть ли вообще какой-нибудь разумный выбор, который компилятор мог бы сделать самостоятельно, не зная намерений вашего кода и иерархии классов?

Да, написание такой функциональности самостоятельно будет подвержено той же проблеме, но там программист имеет полный контроль над выбором поведения, а не компилятор.

1
ответ дан 18 December 2019 в 05:26
поделиться

Он вызывается с помощью вызова виртуальной функции. Передайте процессор * виртуальному методу DerivedA / B. А не наоборот.

Механизма нет, потому что он совершенно ненужен и избыточен.

Клянусь, я ответил на этот вопрос день или два назад.

4
ответ дан 18 December 2019 в 05:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: