Объясните разницу между шаблонами в C++ и дженериками в Java

Многие программисты полагают, что шаблоны C++ и дженерики (например в Java) — это одно и то же, ведь их синтаксис похож: в обоих случаях можно написать что-то вроде List<T>. Чтобы найти различия, давайте разберемся, что такое шаблоны и дженерики, и как они реализуется в каждом из языков.

Дженерики Java связаны с идеей «стирания типов» (type erasure). Эта техника устраняет параметры типов, когда исходный код преобразуется в байткод JVM.

Предположим, что у вас есть Java-код:

Vector vector = new Vector();
vector.add(new String("hello"));
String str = vector.get(0);

Во время компиляции он будет преобразован:

Vector vector = new Vector();
vector.add(new String("hello"));
String str = (String) vector.get(0);

Использование обобщений Java не повлияло на наши возможности, но сделало код более красивым. Поэтому дженерики в Java часто называют «синтаксическим сахаром».

Дженерики сильно отличаются от шаблонов C++. Шаблоны в C++ представляют собой набор макросов, создающих новую копию шаблонного кода для каждого типа. Особенно это заметно на следующем примере: экземпляр MyClass<Foo> не сможет совместно с MyClass<Bar> использовать статическую переменную. А два экземпляра MyClass<Foo> будут совместно использовать статическую переменную.

Чтобы проиллюстрировать этот пример, рассмотрим следующий код:

/*** MyClass.h ***/
template
class MyClass {
public:
  static int val;
  MyClass(int v){ val = v; }
};

/*** MyClass.cpp ***/
template
int MyClass::val;

template class MyClass;
template class MyClass;

/*** main.cpp ***/
MyClass * foo1 = new MyClass(10);
MyClass * foo2 = new MyClass(15);
MyClass * bar1 = new MyClass(20);
MyClass * bar2 = new MyClass(35);

int f1 = foo1->val; //будет равно 15
int f2 = foo2->val; //будет равно 15
int b1 = bar1->val; //будет равно 35
int b2 = bar2->val; //будет равно 35

В Java различные экземпляры MyClass могут совместно использовать статические переменные, независимо от параметров типа.

Из-за различий в архитектуре дженерики Java и шаблоны C++ имеют множество отличий:

  • Шаблоны C++ могут использовать примитивные типы, как, например, int, а дженерики Java — нет, они обязаны использовать Integer.
  • Java позволяет указывать ограничения на тип, передаваемый в качестве параметра. Например, вы можете использовать дженерики для реализации CardDeck и указать, что параметр типа должен наследоваться от CardGame.
  • В C++ можно создать экземпляр типа, передаваемого параметром, а Java — нет.
  • Java не позволяет использовать типы, передаваемые параметром (например, Foo в MyClass<Foo>) для статических методов и переменных, так как они могут совместно использоваться в MyClass<Foo> и MyClass<Bar>. В C++ — это разные классы, поэтому тип из параметра можно использовать для статических методов и переменных.
  • В Java все экземпляры MyClass<T>, независимо от их параметров, относятся к одному и тому же типу. Параметры типов уничтожаются после компиляции. В C++ экземпляры с разными параметрами типов — различные типы.

Помните, что хотя дженерики Java и шаблоны C++ внешне похожи, это разные вещи.

Разбор задачи по книге «Карьера программиста. Как устроиться на работу в Google, Microsoft или другую ведущую IT-компанию»