Наследование и виртуальные функции против общего программирования - PullRequest
14 голосов
/ 04 ноября 2011

Мне нужно понять, действительно ли Inheritance & virtual functions не требуется в C ++, и можно добиться всего, используя Generic programming. Это пришло от Alexander Stepanov и лекции, которую я смотрел, это Александр Степанов: STL и ее принципы проектирования

Ответы [ 5 ]

15 голосов
/ 04 ноября 2011

Они служат разным целям.Общее программирование (по крайней мере, в C ++) - это полиморфизм времени компиляции, а виртуальные функции - полиморфизм времени исполнения.

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

14 голосов
/ 04 ноября 2011

Мне всегда нравится думать о шаблонах и наследовании как о двух ортогональных концепциях в буквальном смысле: для меня наследование идет "вертикально", начиная с базового класса сверху и опускаясь "вниз" «Все больше и больше производных классов. Каждый (публично) производный класс является базовым классом с точки зрения его интерфейса: пудель - это собака, это животное.

С другой стороны, шаблоны идут "горизонтально": каждый экземпляр шаблона имеет одинаковое формальное содержимое кода, но два отдельных экземпляра - это совершенно отдельные, не связанные между собой части, которые выполняются в «параллели» и не видимся. Сортировка массива целых чисел формально аналогична сортировке массива чисел с плавающей точкой, но массив целых чисел никак не связан с массивом чисел с плавающей точкой.

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

Наследование - это создание абстрактной концепции, которая становится все более конкретной путем добавления деталей. Общее программирование - это, по сути, генерация кода .

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

// internal helper base
class TEBase { /* ... */ };

// internal helper derived TEMPLATE class (unbounded family!)
template <typename T> class TEImpl : public TEBase { /* ... */ }

// single public interface class
class TE
{
  TEBase * impl;
public:
  // "infinitely many" constructors:
  template <typename T> TE(const T & x) : impl(new TEImpl<T>(x)) { }
  // ...
};
2 голосов
/ 04 ноября 2011

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

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

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

Некоторое время назад я думал о похожем вопросе и могу только мечтать дать вам такой замечательный ответ, который я получил.Возможно, это полезно: производительность парадигмы интерфейса (динамическое связывание или общее программирование)

1 голос
/ 04 ноября 2011

Кажется, это очень академический вопрос, так как с большинством вещей в жизни существует множество способов сделать что-то, а в случае с C ++ у вас есть несколько способов решить вещи. Нет необходимости иметь отношение XOR к вещам.

0 голосов
/ 28 июля 2012

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

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

Я выполнил несколько сложных обобщенных программ, используя статический полиморфизм для реализации универсальной библиотеки RPC (https://github.com/bytemaster/mace (ветвь rpc_static_poly)).В этом случае протокол (JSON-RPC, транспорт (TCP / UDP / Stream / и т. Д.) И типы) все известны во время компиляции, поэтому нет никаких причин делать vtable диспетчеризацию ... или есть?

Когда я запускаю код через препроцессор для single.cpp, получается 250 000 строк и более 30 секунд на компиляцию одного объектного файла.Я реализовал «одинаковую» функциональность в Java и C #, и она компилируется примерно за секунду.

Почти каждый заголовок stl или boost добавляет тысячи или десятки тысяч строк кода, которые должны обрабатываться для каждого объекта.файл, большая часть которого избыточна.

Имеет ли значение время компиляции?В большинстве случаев они оказывают более существенное влияние на конечный продукт, чем «максимально оптимизированное удаление vtable».Причина в том, что для каждой «ошибки» требуется цикл «попробовать исправить, скомпилировать, протестировать», и если каждый цикл занимает более 30 секунд, разработка замедляется до ползания (обратите внимание на мотивацию языка go Google).

Проведя несколько дней с Java и C #, я решил, что мне нужно «переосмыслить» свой подход к C ++.Нет причин, по которым программа на C ++ должна компилироваться намного медленнее, чем базовый C, который будет реализовывать ту же функцию.

Теперь я выбираю полиморфизм во время выполнения, если только профилирование не показывает, что узкое место находится в виртуальных отправлениях.Теперь я использую шаблоны, чтобы обеспечить полиморфизм «точно в срок» и типобезопасный интерфейс поверх базового объекта, который имеет дело с «void *» или абстрактным базовым классом.Таким образом, пользователям не нужно извлекать выгоду из моих «интерфейсов», и они по-прежнему ощущают «общее» программирование, но они получают преимущество быстрого времени компиляции.Если производительность становится проблемой, то общий код можно заменить статическим полиморфизмом.

Результаты впечатляющие, время компиляции сократилось с 30 с лишним секунд до примерно секунды.Исходный код постпроцессора теперь составляет пару тысяч строк вместо 250 000 строк.

С другой стороны дискуссии я разрабатывал библиотеку «драйверов» для набора похожих, но немного разных встроенных устройств.В этом случае во встроенном устройстве было мало места для «дополнительного кода» и не было необходимости для отправки «vtable».С C нашей единственной опцией были «отдельные объектные файлы» или «полиморфизм» времени выполнения через указатели функций.Используя общее программирование и статический полиморфизм, мы смогли создать поддерживаемое программное обеспечение, которое работало быстрее, чем все, что мы могли производить в C.

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