Хотелось бы задать такой вопрос: неоднократно слышал, что обобщенные типы (generics
в C# менее мощны, нежели шаблоны в C++. Но вот каких-либо доводов в пользу (или против) этого утверждения я не слышал. Действительно ли это так, если да, то в чем именно это проявляется.
P.S. От себя могу лишь добавить, что недавно столкнулся с такой досадной и странной особенностью, что если SomeClassChild — потомок класса SomeClass, то
List нельзя преобразовать к List, тогда как SomeClassChild[] к SomeClass[] — можно.
Более того, следующий код также приведет к ошибке:
List lst = new List();
lst.Add(new SomeClassChild());
Ответы
Ответ 1
Разница на самом деле большая.
Шаблоны C++ -- продвинутая версия макросов. В C# же генерики -- конструкция времени выполнения. Это определяет разницу. Например, такое:
template
T add(T lhs, T rhs)
{
return lhs + rhs;
}
проходит в C++: во время компиляции шаблона компилятор точно знает тип T и знает
может ли к нему применяться оператор +. Аналогичная конструкция не сработает в C#: компилятор генерирует один код для всех на все случаи, и он не знает, что такое + для произвольных классов. Можно, конечно, обойти проверку при помощи такого трюка:
T add(T lhs, T rhs)
{
dynamic l = lhs;
dynamic r = rhs;
return (T)(l + r);
}
-- но эта конструкция выявит отсутствие оператора + во время выполнения, а не компиляции.
Из того, что генерики -- конструкция времени выполнения, выплывают и другие отличия
В частности, в генериках, в отличие от шаблонов C++, нельзя сделать отдельную специализацию для конкретного типа параметров, или для некоторого их набора. (Для шаблонов это называется "частичная/явная специализация".)
Теперь по поводу вашего примера:
List lderived = new List();
List lbase = lderived; // не компилируется
То, что приведённая строка не компилируется -- правильно. И не должно.
Представьте себе, что этот код таки скомпилировался. Тогда вы сможете написать:
lbase.Add(new SomeClass());
А это уже плохо, потому что теперь в lderived появился экземпляр чужого класса! То
что SomeClassChild[] можно привести к SomeClass[] -- это "тяжёлое наследие" старых версий языка, когда не была реализована ковариантность генериков. (Вставить элемент чужого класса всё равно на получится, вылетит с исключением.)
По поводу
List lst = new List();
lst.Add(new SomeClassChild());
-- это должно работать. Перепроверьте!
Следует отметить, что у генериков C# есть и преимущества по сравнению с C++. Например
код генерика один и тот же для всех специализаций (на уровне IL, JIT-компилятор може
ради оптимизации сделать несколько разных специализаций на уровне нативного кода). Поэтом
вы можете компилировать код, использующий генерик, даже не имея полного исходного текста генерика. Для шаблонов C++ это невозможно: шаблон должен быть перекомпилирован для каждого из типов, с которым он используется. Эта особенность существенно замедляет компиляцию шаблонов в C++ по сравнению с C#, где генерик компилируется лишь один раз.
Дополнение: поскольку в C++ все инстанциации шаблона должны быть известны во врем
компиляции, это не позволяет динамически решать, с каким типом будет инстанциирован шаблон (что ограничивает семантику программ). У C# инстанциация шаблона происходит во время выполнения, и проблем не возникает. Пример: C#, C++.
Ответ 2
Шаблоны в Си++ компилируются под каждый конкретный аргумент в новый кусок кода. Эт
позволяет использовать в них в полной мере все возможности того типа, который в них подставлен.
Генерики в C# компилируются в код, который может быть применён для любого переданног
в них типа (подходящего под объявленное условие). Из-за этого сразу теряются следующие возможности:
Использование статических методов генерик-типа, а следовательно всех операторов (кром
default property, конечно). Есдинственное исключение сделано для new без параметров. И то он вызывается рефлексией, поскольку другого способа в .net-рантайме нет.
Возможность наследоваться от генерик-параметра.
Возможность использовать какие-либо методы, наличие которых не гарантируется перечисленными в where ограничениями на классы и интерфейсы.
Шаблоны в C# компилируются в код, подходящий под любой допустимый параметр. Т. е
шаблон в шарпе - это просто функция - обычная функция, которая будет исполняться так
как ей предписано, независимо от реального типа. Все вызовы строятся на основе объявленных ограничений на generic-тип. Реальный тип кикак не используется. Поэтому по-другому работают такие вещи как:
Вызов невиртуальных методов и методов интерфейса. Вызывается метод базового класса, а не реального, как было бы в Си++.
http://ideone.com/SfnNff
Разрешение перегрузки функций происходит во время компиляции, когда сведения о конкретном типе неизвестны.
http://ideone.com/DKCu9V
Вроде было ещё что-то, но сейчас не могу вспомнить.
Ответ 3
Ещё одно преимущество шаблонов C++ заключается в том, что они позволяют параметризоват
функцию или класс не только типом, но и значением. Ещё в последней версии C++ появились шаблоны с переменным числом параметров.
Комментариев нет:
Отправить комментарий