С# generics vs С++ templates - требуется разъяснение о ограничениях

Duplicate

Каковы различия между Generics в С# и Java... и шаблонами в С++?


Привет всем,

Я опытный программист на С++, но совершенно новый для С#.

Что связано с этими ограничениями и дженериками? Почему он не работает так же, как в С++, где ограничения являются неявными и производными от экземпляров, которые вы делаете с классом шаблона?

Почему Microsoft не работала так же, как на С++?

Ответ 1

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

Самая большая разница между С# generics и шаблонами С++ - это то, что генераторы С# лучше проверяют тип. Они всегда ограничены в том смысле, что они не позволяют выполнять операции, которые не указаны в момент определения дженериков. Главный конструктор С#, поднятый в качестве причины того, что добавленная сложность потребовала бы иметь подразумеваемые ограничения. Я не очень разбираюсь в С#, поэтому я не могу говорить дальше. Я расскажу о том, как обстоят дела на С++ и как они будут улучшаться, чтобы люди не думали, что материал С++ ошибочен.

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

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

Это фундаментальная проблема, которую можно решить, просто указав на интерфейс шаблона или обобщения, какие свойства должен иметь параметр типа. С#, насколько я знаю, может ограничивать параметр для реализации интерфейса или наследования базового класса. Он решает это на уровне типа.

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

template<typename T> requires VariableType<T>
T f(T a, T b) {
    return a + b; 
}

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

template<typename T> requires VariableType<T> && HasPlus<T, T>
T f(T a, T b) {
    return a + b; 
}

Теперь он будет компилятором. Компилятор, увидев, что T появляется как возвращаемый тип, автоматически подразумевает, что T является скопируемым, поскольку это использование T появляется в интерфейсе, а не в корпусе шаблонов. Другие требования были сформулированы с использованием условий требования. Теперь пользователь получит соответствующее сообщение об ошибке, если он использует тип, который не имеет op+.

С++ 1x отделяет требования от типа. Вышеприведенное работает как для примитивных типов, так и для классов. В этом смысле они более гибкие, но довольно сложные. Правила, которые определяют, когда и когда требования удовлетворяются, длинные... Вы можете с новыми правилами сказать следующее:

template<typename T> requires MyCuteType<T>
void f(T t) { *t = 10; }

А затем вызовите f с помощью int! Это будет работать, просто написав концептуальную карту для MyCuteType<int>, которая учит компилятору, как можно разыменовать int. Это будет очень удобно в таких циклах:

for_each(0, 100, doSomething());

Так как программист может рассказать компилятору, как int может удовлетворить концепцию input iterator, вы могли бы написать такой код в С++ 1x, если бы вы только написали соответствующую концептуальную карту, которая действительно не все это сложно.

Хорошо, достаточно с этим. Надеюсь, я смогу показать вам, что наличие ограниченных шаблонов не так уж плохо, но на самом деле лучше, потому что отношения между типами betweens и операциями над ними в шаблонах теперь известны компилятору. И я даже не писал о axioms, что еще одна приятная вещь в концепциях C++1x. Помните, что это будущий материал, он еще не вышел, но он будет примерно в 2010 году. Тогда нам придется подождать, пока какой-то компилятор выполнит все:)


ОБНОВЛЕНИЕ ОТ "БУДУЩЕГО"

Концепции С++ 0x не были приняты в проект, но были проголосованы в конце 2009 года. Слишком плохо! Но, возможно, мы увидим это снова в следующей версии С++? Пусть все надеются!

Ответ 2

С++ templates: Компилятор проверяет, удовлетворяют ли аргументы ограничениям, установленным кодом. Например:

template <typename T, unsigned int dim>
class math_vector
{
    T elements[dim];

    math_vector<T,dim> operator+ (const math_vector<T,dim>& other) const
    {
        math_vector<T,dim> result;
        for (unsigned int i = 0; i < dim; ++i)
            result.elements[i] = elements[i] + other.elements[i];
    }
}

struct employee
{
    char name[100];
    int age;
    float salary;
}

math_vector<int, 3> int_vec; //legal
math_vector<float, 5> float_vec; //legal
math_vector<employee, 10> employee_vec; //illegal, operator+ not defined for employee

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

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

class SortedList<T> where T : IComparable<T>
{
    void Add(T i) { /* ... */ }
}

class A : IComparable<A> { /* ... */ }

class B
{
    int CompareTo(B b) { /* ... */ }
    bool Equals(B b) { /* ... */ }
}

SortedList<A> sortedA; // legal
SortedList<B> sortedB; // illegal
// B implements the methods and properties defined in IComparable,
// however, B doesn't explicitly implement IComparable<B>

Ответ 3

Вскоре вы получите лучший ответ, я уверен. В это время я удалю этот.

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

Generics в .NET(также в VB.NET) - это среда выполнения. Это особый тип. Ограничения необходимы, чтобы гарантировать, что любое фактическое использование этого типа будет действительным при окончательном использовании типа.

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

Ответ 4

Генераторы С# полностью отличаются от С++.

В С# компилятор в основном компилирует одно определение класса для всех типов объектов и определение класса для типа значения.

В С++ каждый тип получает свои собственные определения классов.

Ограничения предназначены только для компилятора, поэтому вы можете вывести материал из других мест.

Я бы рекомендовал посмотреть делегаты Action, Func и Predicate и связанные с ними методы расширения IEnumerable. Используйте лямбда-функции с ними, и вы увидите, что делают ограничения.

Ответ 5

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

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

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

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