Почему C ++ не позволяет запрашивать указатель на самый производный класс? - PullRequest
5 голосов
/ 16 июня 2010

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

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

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

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

Но этот механизм не предусмотрен в c ++.Почему?

Обновление, Пример мотивации:

Предположим, что вместо Base, Derived и Processor у вас есть:

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» сделает это намного проще?* Я думаю, что Pontus Gagge имеет хороший ответ.Добавьте к этому этот бит из записи Википедии о Multiple Dispatch :

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

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

Ответы [ 10 ]

8 голосов
/ 16 июня 2010

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

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

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

8 голосов
/ 16 июня 2010

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

6 голосов
/ 16 июня 2010

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

4 голосов
/ 16 июня 2010

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

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

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

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

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

4 голосов
/ 16 июня 2010

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

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

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

2 голосов
/ 16 июня 2010

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

2 голосов
/ 16 июня 2010

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

1 голос
/ 17 июня 2010

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

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

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

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

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

1 голос
/ 16 июня 2010

Это невозможно в 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);
}
0 голосов
/ 16 июня 2010

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

...