Можно ли полностью избежать шаблонов в C ++? - PullRequest
4 голосов
/ 19 июля 2011

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

НАЧАЛО настройки

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

class universal_base
{
};

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

template <typename T>

class C
{
   T &x;
   int f(T &y);
   C(T &z): x(z + 1) {}
};

class C
{
   universal_base &x;
   int f(universal_base &y);
   C(universal_base &z): x(z + 1) {}
};

Разница в том, что в первой конструкции выражение z + 1не может быть гарантированно действительным;Вы просто должны сказать пользователям, что T должен перегрузить operator+.Во второй конструкции я мог бы добавить виртуальный такой оператор к universal_base:

// in universal_base
public:
 virtual universal_base& operator+(const universal_base &x) = 0;

и использовать typeid и dynamic_cast в реализациях, чтобы получить правильный аргумент.Таким образом, невозможно написать плохо сформированный код, потому что компилятор будет жаловаться, если вы не реализуете operator+.

Конечно, таким способом невозможно объявить элементы не ссылкамиТип:

class C: public universal_base
{
  universal_base x; // Error: universal_base is a virtual type
};

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

template <typename T>
class C: public universal_base
{
  T x;
};

, я бы почти наверняка дал ему объекты типа T в какой-то момент.В этом случае нет причины, по которой я не смог бы сделать следующее:

class universal_base
{
  public:
   virtual universal_base& clone() = 0;
};

class C: public universal_base
{
  universal_base &x;
  C(universal_base &y) : x(y.clone()) {}
}

По сути, я создаю переменную типа, который определяется во время выполнения.Это, конечно, требует, чтобы каждый объект типа C был должным образом инициализирован, но я не думаю, что это огромная жертва.

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

END setup

Итак, мой вопрос: полностью ли это заменяет шаблоны, по модулю что-то про инициализацию?Это неэффективно или опасно как-то?

Ответы [ 7 ]

13 голосов
/ 19 июля 2011

Шаблоны обеспечивают полиморфизм во время компиляции;Виртуальные функции обеспечивают полиморфизм во время выполнения.

Конечно, вы ничего не можете сделать во время компиляции, что вы не можете сделать во время выполнения.Различия:

  1. Производительность
  2. Проверка во время компиляции

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

Таким образом, производительность, безусловно, является одной из проблем.

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

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

4 голосов
/ 19 июля 2011

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

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

4 голосов
/ 19 июля 2011

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

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

Редактировать в ответ на ваш комментарий ...

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

  • Оптимизация во время компиляции: встраивание и последующая оптимизация, такая как устранение мертвого кода, развертывание цикла фиксированного размера.
  • шаблон CURLY Recurring Template Pattern (CRTP) обеспечивает реализацию, вызываемую в конструкторе,
  • Сбой замещения не является ошибкой (SFINAE) позволяет более интеллектуально разрешать вызовы функций, включая ограниченную возможность самоанализ на интерфейсе интерфейса.тип параметра
  • Шаблонные классы могут предоставлять функции, которые работают для одного типа / значения параметра, но не для других, генерируя ошибку времени компиляции, только если эти функции фактически используются для неподдерживаемого параметра (например, + можетбыть «перенаправлен» на параметр + - whпростой член или не член - но только при наличии)
  • Специализация: позволяет шаблонам обрабатывать особые случаи по-разному.
  • Понятия (которых пока нет в C ++ - не сделает C)++ 11 тоже - но чтение о них подчеркнет, как шаблоны зависят от семантики, что более гибко, чем сигнатуры функций set в виртуальной диспетчеризации)
  • Шаблоны являются формой параметрического полиморфизма, поэтому клиентский код может простопопытайтесь использовать их, не вводя базовый класс, или не занимая место для указателя виртуальной отправки.
    • Полиморфизм времени выполнения не работает с несколькими процессами, обращающимися к объектам в общей памяти, так как указатели виртуальной диспетчеризации, установленные одним процессом, могут не указывать правильное место для другого.
  • Избегайте (медленной) кучи.Виртуальная диспетчеризация обычно включает создание объектов в куче, поскольку их размер изменяется в зависимости от иерархии деривации и не известен во время компиляции.Шаблоны избегают этих затрат.
    • Размеры массивов иногда можно указывать в качестве параметров шаблона, избегая динамического выделения.
3 голосов
/ 19 июля 2011

Я думаю, что аргументы против универсального класса "Object" были бы уместны здесь:

Почему в C ++ нет универсального класса Object?

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

Я бы, вероятно, использовал бы интерфейс C для такого модуля: напишите динамический, безопасный для типов бэкэнд в C ++ с шаблонами, затем обертку с внешними функциями "C", чтобы использовать его.

2 голосов
/ 19 июля 2011

Нет, это короткий и сладкий ответ.

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

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

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

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

2 голосов
/ 19 июля 2011

Общий подход, который вы описываете, довольно распространен, особенно в Java.Вы можете столкнуться с проблемами, пытаясь обобщить это до той точки, которую вы описываете.Например, если вы заставляете всех внедрять оператор +, вы также заставляете всех внедрять все остальные операторы?(+ =, - =, /, ++, - и т. д.), и если вы это сделаете, вы в конечном итоге победите большую часть цели проверки типов (т. е. если вы разрешите всем типам преобразовывать во все остальное и иметь всеоператоры), и в результате у ваших типов будут фиктивные реализации операторов, которые не имеют смысла для типа.

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

0 голосов
/ 19 июля 2011

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

На самом деле, общий шаблон, который вы ищете, выглядит как интерфейс и реализация.

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

Например:

class IAddable
{
public:
    virtual IAddable * Add(IAddable * pOther) = 0;
};

class Number
    : public IAddable
{
public:
    virtual IAddable * Add(IAddable * pOther)
    {
        return new Number(this, pOther);
    }

    Number(Number * a, Number * b)
        : m_Value(a->m_Value + b->m_Value)
    { };

private:
    int m_Value;
};

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

Что касается использования виртуальных операторов, если такая возможность возможна (не использовал их сам), кажется, что она будет работать.

Что-торассмотрите возможность проверки двух операндов, для которых вы можете определить проверку типа в вашем интерфейсе (аналогично COM * IUnknown::QueryInterface);

Вы также должны посмотреть, как вы обрабатываете наследованиетипы;будет задействовано много указателей, поскольку ни вы, ни компилятор не могут знать, что они собой представляют.Удостоверьтесь, что вы проверяете ошибки и неправильные типы довольно часто (возвращайте значения ошибок или конвертируйте типы, или throw, или what-have-you).

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