Подтвердить что ты не робот

Существует ли реальный статический полиморфизм в С++?

Вот простой код в С++:

#include <iostream>
#include <typeinfo>

template<typename T>
void function()
{
   std::cout << typeid(T).name() << std::endl;
}

int main()
{
   function<int>();
   function<double>();
   return 0;
}

Я читал, что шаблоны в С++ - это функция времени компиляции, которая не похожа на генераторы в С#/Java.

Итак, как я понял, компилятор С++ делит одну определенную функцию на различные числа (зависит от количества вызовов с разными типами) функций.

Я прав, или нет? Я не эксперт в компиляторах на С++, поэтому я прошу вас посоветоваться.

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

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

4b9b3361

Ответ 1

Существует ли реальный статический полиморфизм в С++?

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

Итак, как я понял, компилятор С++ делит одну определенную функцию на различное число (зависит от количества вызовов с другим типом) функций. Я прав или нет?

Это общая идея. Количество создаваемых экземпляров зависит от количества перестановок параметров шаблона, которые могут быть явно указаны как в function<int> и function<double> или - для шаблонов, которые используют параметры шаблона для соответствия аргументам функции - автоматически получаются из функции аргументы, например:

template <typename T, size_t N>
void f(T (&array)[N])
{ }

double d[2];
f(d);   // instantiates/uses f<double, 2>()

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


Я хочу знать, могу ли я описать вышеприведенный код как статический полиморфизм?

Не совсем.

  • template<> function создается для двух типов

    • критически, полиморфизм не используется для выбора того из двух экземпляров function для отправки на сайты вызовов

    • тривиально, в течение таких экземпляров typeid(T) оценивается для int и double и эффективно ведет себя полиморфно с точки зрения программиста (это ключевое слово компилятора, хотя реализация неизвестна)

  • тривиально, сочетание статического и номинально динамического (но скорее всего оптимизируемого для статического) полиморфизма поддерживает использование std::cout

Фон - полиморфизм и генерация кода

Требование, которое я считаю решающим для полиморфизма, следующее:

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

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

    • например, std::cout << x; полиморфно вызывает другой код, поскольку тип x изменяется, но все же выдает значение x, тогда как неполиморфный printf("%d", x) обрабатывает int, но должен быть вручную изменен на printf("%c", x);, если x становится char.

Но то, что мы пытаемся достичь с помощью полиморфизма, немного более общее:

  • повторное использование алгоритмического кода для нескольких типов данных без внедрения явного определения типа и кода ветвления

    • то есть без исходного кода программы, содержащего if (type == X) f1(x) else f2(x); -стильный код
  • снижение нагрузки на обслуживание, так как после явного изменения типа переменной меньшее количество последующих изменений необходимо выполнить вручную в исходном коде

Эти аспекты большего размера поддерживаются в С++ следующим образом:

  • экземпляр того же исходного кода, чтобы генерировать отличные поведения (машинный код) для некоторого другого типа strong > или перестановка типов (это аспект параметрического полиморфизма),

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

... и несколькими незначительными способами за мой ответ в Полиморфизм в С++

Различные типы полиморфизма включают один или оба из них:

  • отправка (2) может произойти во время создания (1) для шаблонов и препроцессора макросов,

  • (1) обычно бывает при отправке (2) для шаблонов (без соответствующей полной специализации) как макросы(вид циклических, хотя макросы не рекурсивно расширяются)

  • отправка (2) может произойти без (1), когда компилятор выбирает ранее существовавшую функцию перегрузка или , или когда компилятор запускает виртуальную/динамическую отправку.

Что действительно использует ваш код?

function<int> и function<double> повторно используйте код шаблона function, чтобы создать отдельный код для каждого из этих типов, поэтому вы получаете экземпляр (1), как указано выше. Но вы жестко кодируете, какие экземпляры вызова, а не компилятор, неявно выбирают экземпляр, основанный на типе некоторого параметра, т.е. Вы не используете прямое использование неявной отправки ala (2) при вызове function. Действительно, function не имеет параметра, который компилятор мог бы использовать для неявного выбора экземпляра шаблона.

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

Итак, что было бы однозначно полиморфным?

Для того, чтобы проиллюстрировать, как шаблоны могут поддерживать отправку (2), а также создание экземпляра (1) и бесспорный обеспечивают "полиморфизм", рассмотреть следующие вопросы:

template<typename T>
void function(T t)
{
    std::cout << typeid(T).name() << std::endl;
}

function(4);      // note: int argument, use function<int>(...)
function(12.3);   // note: double argument, use function<double>(...)

В приведенном выше коде также используется неявная отправка для соответствующего кода кода - "2.". выше - полиморфизма.


Нестандартные параметры

Интересно, что С++ предоставляет возможность создавать шаблоны с интегральными параметрами, такими как boolean, int и константами указателей, и использовать их для всех вещей без изменения ваших типов данных и, следовательно, без какого-либо полиморфизма. Макросы еще более гибкие.


Обратите внимание, что использование шаблона в C.R.T.P. стиль НЕ является обязательным для статического полиморфизма - это пример его применения. Во время создания экземпляра компилятор демонстрирует статический полиморфизм при сопоставлении операций с реализациями в указанном параметре.


Обсуждение терминологии

Получение окончательного определения полиморфизма затруднено. wikipedia цитирует онлайн-словарь Bjarne Stroustrup "Предоставление единого интерфейса для объектов разных типов": это означает, что struct X { void f(); }; struct Y { void f(); }; уже проявляет полиморфизм, но IMHO мы получаем только полиморфизм, когда мы используем соответствие интерфейса с клиентским кодом, например. template <typename T> void poly(T& t) { t.f(); } требует статической полиморфной отправки t.f() для каждого экземпляра.

Ответ 2

В Википедии перечислены три типа полиморфизма:

  • Если функция обозначает разные и потенциально гетерогенные реализации в зависимости от ограниченного диапазона индивидуально заданных типов и комбинаций, он называется ad hoc polymorphism. Для этого случая полиморфизм поддерживается на многих языках, используя функцию перегрузка.

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

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

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

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

Например:

template <typename T, typename U>
auto func(const T& t, const U& u) -> decltype(t + u)
{
   return (t + u);
}

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

Однако в вашем примере у вас есть экземпляры для ваших различных функций, function<int> и function<double>. Здесь цитата:

Чтобы быть полиморфным, [a()] должен иметь возможность работать со значениями at наименее два разных типа (например, int и double), поиск и выполнение тип-соответствующий код.

В этом случае экземпляры специфичны для типа, в котором они были созданы, поэтому не существует никакого полиморфизма.

Ответ 3

В вашем примере нет статического полиморфизма, потому что полиморфизма нет. Это связано с тем, что function<int>() не выглядит так же, как function<double>().

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

#include <iostream>
#include <typeinfo>

template<typename T>
void function(T)
{
   std::cout << typeid(T).name() << std::endl;
}

int main()
{
   function(0);   // T is int
   function(0.0); // T is double
   return 0;
}

Вот еще один пример:

template<typename T>
void function(T t)
{
  t.foo();
}

struct Foo() 
{
  void foo() const {}
};

struct Bar() 
{
  void foo() const {}
};

int main()
{
  Foo f;
  Bar b;
  function(f); // T is Foo
  function(b); // T is Bar
}

Ответ 4

В случае С++ термин "статический полиморфизм" обычно используется, например, шаблоны проектирования типа CRTP:

template<typename Derived> 
class Base
{
      void someFunc() {
          static_cast<Derived*>(this)->someOtherFunc();
      };
};

class ADerived : public Base<ADerived>
{
    void someOtherFunc() { 
        // ... 
    }
};

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

Ответ 5

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

template<class T>
class Base
{
public:
      int a() { return 7; }
};

template<>
class Base<int>
{
public:
      int a() { return 42; }
};

template<>
class Base<double>
{
public:
      int a() { return 121; }
};

Мы видим здесь, что для большинства классов a() вернет 7; Специализированные (производные) instanciations для int и double могут иметь радикально различное поведение, продемонстрированное в простом случае разными значениями возврата, то же самое можно сделать для шаблонов с, например, параметрами int, и может показать, что может быть странно называемый статическим рекурсивным полиморфизмом.

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