Обобщения C # против шаблонов C ++ - необходимо разъяснить ограничения - PullRequest
11 голосов
/ 12 апреля 2009

Дублирование

В чем различия между Обобщением в C # и Java ... и Шаблонами в C ++?


Привет всем,

Я опытный программист на C ++, но довольно плохо знаком с C #.

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

Почему Microsoft не заставила его работать так же, как в C ++?

Ответы [ 5 ]

20 голосов
/ 12 апреля 2009

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

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

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

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

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

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

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+.

C ++ 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, вы могли бы написать такой код на C ++ 1x, если бы вы только написали соответствующую карту концептов, что на самом деле не все это сложно.

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


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

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

4 голосов
/ 12 апреля 2009

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

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. Следовательно, параметр шаблона действителен тогда и только тогда, когда он удовлетворяет ограничениям, определенным кодом шаблона. Это очень гибко, но приводит к длительному времени компиляции (каждый раз, когда создается экземпляр шаблона, нужно проверять, удовлетворяет ли тип ограничениям шаблона).

C # 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 голосов
/ 12 апреля 2009

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

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

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

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

1 голос
/ 12 апреля 2009

Обобщения C # полностью отличаются от C ++.

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

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

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

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

0 голосов
/ 12 апреля 2009

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

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

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

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

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