Вопрос:

Каковы различия между «универсальными» типами в C ++ и Java?

java c++ generics templates language-features

81966 просмотра

12 ответа

5490 Репутация автора

У Java есть дженерики, а C ++ предоставляет очень сильную модель программирования с templates. Итак, в чем же разница между обобщениями C ++ и Java?

Автор: popopome Источник Размещён: 30.08.2008 09:14

Ответы (12)


74 плюса

129256 Репутация автора

C ++ имеет шаблоны. У Java есть дженерики, которые выглядят как шаблоны C ++, но они очень разные.

Шаблоны работают, как следует из названия, предоставляя компилятору шаблон (подождите его ...), который он может использовать для генерации безопасного кода, заполняя параметры шаблона.

Обобщения, как я их понимаю, работают наоборот: параметры типа используются компилятором для проверки того, что код, использующий их, безопасен для типов, но полученный код генерируется без типов вообще.

Думайте о шаблонах C ++ как о действительно хорошей макросистеме, а обобщения Java как о инструменте для автоматического создания типов типов.

 

Автор: Shog9 Размещён: 30.08.2008 09:30

123 плюса

6793 Репутация автора

Решение

Между ними есть большая разница. В C ++ вам не нужно указывать класс или интерфейс для универсального типа. Вот почему вы можете создавать действительно универсальные функции и классы с оговоркой более свободной типизации.

template <typename T> T sum(T a, T b) { return a + b; }

Приведенный выше метод добавляет два объекта одного типа и может использоваться для любого типа T, для которого доступен оператор «+».

В Java вы должны указать тип, если вы хотите вызывать методы для переданных объектов, например:

<T extends Something> T sum(T a, T b) { return a.add ( b ); }

В C ++ универсальные функции / классы могут быть определены только в заголовках, так как компилятор генерирует разные функции для разных типов (с которыми он вызывается). Таким образом, сборка идет медленнее. В Java компиляция не имеет серьезного наказания, но Java использует технику, называемую «стирание», где универсальный тип стирается во время выполнения, поэтому во время выполнения Java фактически вызывает ...

Something sum(Something a, Something b) { return a.add ( b ); }

Таким образом, общее программирование на Java не очень полезно, это всего лишь немного синтаксического сахара, чтобы помочь с новой конструкцией foreach.

РЕДАКТИРОВАТЬ: мнение выше о полезности было написано молодым человеком. Обобщения Java помогают, конечно, с безопасностью типов.

Автор: Alexandru Nedelcu Размещён: 30.08.2008 09:34

3 плюса

80354 Репутация автора

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

Автор: Ferruccio Размещён: 03.09.2008 12:00

3 плюса

14134 Репутация автора

Еще одним преимуществом шаблонов C ++ является специализация.

<typename T> T sum(T a, T b) { return a + b; }
<typename T> T sum(T* a, T* b) { return (*a) + (*b); }
Special sum(const Special& a, const Special& b) { return a.plus(b); }

Теперь, если вы вызовете sum с помощью указателей, будет вызван второй метод, если вы вызовете sum для объектов без указателей, будет вызван первый метод, а если вы вызовете sum () для объектов Special, будет вызван третий. Я не думаю, что это возможно с Java.

Автор: KeithB Размещён: 03.09.2008 03:23

1 плюс

399202 Репутация автора

@Keith:

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

template <typename T> T sum(T a, T b) { return a + b; }
template <typename T> T sum(T* a, T* b) { return (*a) + (*b); }
Автор: Konrad Rudolph Размещён: 03.09.2008 03:50

4 плюса

143035 Репутация автора

По сути, шаблоны AFAIK, C ++ создают копию кода для каждого типа, в то время как шаблоны Java используют точно такой же код.

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

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

от: Java Generics

Автор: OscarRyz Размещён: 31.01.2009 05:18

115 плюса

507994 Репутация автора

Java Generics сильно отличается от шаблонов C ++.

В основном в C ++ шаблоны в основном представляют собой прославленный набор препроцессоров / макросов ( Примечание: поскольку некоторые люди не могут понять аналогию, я не говорю, что обработка шаблонов - это макрос). В Java они в основном являются синтаксическим сахаром для минимизации шаблонного приведения объектов. Вот довольно приличное введение в шаблоны C ++ против обобщений Java .

Чтобы уточнить этот момент: когда вы используете шаблон C ++, вы в основном создаете еще одну копию кода, как если бы вы использовали #defineмакрос. Это позволяет вам делать такие вещи, как иметь intпараметры в определениях шаблонов, которые определяют размеры массивов и тому подобное.

Java не работает так. В Java все объекты выходят из java.lang.Object, поэтому, прежде чем Generics, вы бы написали код, подобный этому:

public class PhoneNumbers {
  private Map phoneNumbers = new HashMap();

  public String getPhoneNumber(String name) {
    return (String)phoneNumbers.get(name);
  }

  ...
}

потому что все типы коллекций Java использовали Object в качестве базового типа, чтобы вы могли помещать в них что угодно. Java 5 катится и добавляет дженерики, чтобы вы могли делать такие вещи, как:

public class PhoneNumbers {
  private Map<String, String> phoneNumbers = new HashMap<String, String>();

  public String getPhoneNumber(String name) {
    return phoneNumbers.get(name);
  }

  ...
}

И это все, что являются Java Generics: обертки для приведения объектов. Это потому, что Java Generics не улучшены. Они используют стирание типа. Это решение было принято, потому что Java Generics появились настолько поздно, что они не хотели нарушать обратную совместимость (a Map<String, String>может использоваться всякий раз, когда требуется a Map). Сравните это с .Net / C # , где типа стирание не используется, что приводит к разному роду различий (например , вы можете использовать примитивные типы и IEnumerableи IEnumerable<T>не имеете никакого отношения друг к другу).

А класс, использующий дженерики, скомпилированные с помощью компилятора Java 5+, можно использовать в JDK 1.4 (при условии, что он не использует никаких других функций или классов, требующих Java 5+).

Вот почему Java Generics называют синтаксическим сахаром .

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

Шаблоны C ++ имеют ряд функций, которых нет в Java Generics:

  • Использование аргументов примитивного типа.

    Например:

    template<class T, int i>
    class Matrix {
      int T[i][i];
      ...
    }
    

    Java не позволяет использовать аргументы примитивного типа в обобщениях.

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

  • Java позволяет ограничивать аргументы.

Например:

public class ObservableList<T extends List> {
  ...
}

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

Помимо различий с дженериками, для полноты приведем базовое сравнение C ++ и Javaеще одного ).

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

Многое из того, что вы узнаете при изучении Java, - это все библиотеки (обе стандартные - то, что входит в JDK - и нестандартные, которые включают в себя часто используемые вещи, такие как Spring). Синтаксис Java более многословен, чем синтаксис C ++, и не имеет большого количества функций C ++ (например, перегрузка операторов, множественное наследование, механизм деструктора и т. Д.), Но это не делает его строго подмножеством C ++.

Автор: cletus Размещён: 31.01.2009 05:22

13 плюса

12208 Репутация автора

Это прекрасное объяснение этой темы в Java Generics and Collections. Автор Maurice Naftalin, Philip Wadler. Я очень рекомендую эту книгу. Цитировать:

Обобщения в Java напоминают шаблоны в C ++. ... Синтаксис намеренно похож, а семантика сознательно отличается. ... С точки зрения семантики, дженерики Java определяются стиранием, а шаблоны C ++ - расширением.

Пожалуйста, прочитайте полное объяснение здесь .

альтернативный текст
(источник: oreilly.com )

Автор: Julien Chastang Размещён: 31.01.2009 06:16

15 плюса

14134 Репутация автора

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

Автор: KeithB Размещён: 31.01.2009 06:23

0 плюса

1584 Репутация автора

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

Пример:

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

В Java вы можете сделать что-то вроде этого:

import java.io.*;
interface ScalarProduct<A> {
    public Integer scalarProduct(A second);
}
class Nil implements ScalarProduct<Nil>{
    Nil(){}
    public Integer scalarProduct(Nil second) {
        return 0;
    }
}
class Cons<A implements ScalarProduct<A>> implements ScalarProduct<Cons<A>>{
    public Integer value;
    public A tail;
    Cons(Integer _value, A _tail) {
        value = _value;
        tail = _tail;
    }
    public Integer scalarProduct(Cons<A> second){
        return value * second.value + tail.scalarProduct(second.tail);
    }
}
class _Test{
    public static Integer main(Integer n){
        return _main(n, 0, new Nil(), new Nil());
    }
    public static <A implements ScalarProduct<A>> 
      Integer _main(Integer n, Integer i, A first, A second){
        if (n == 0) {
            return first.scalarProduct(second);
        } else {
            return _main(n-1, i+1, 
                         new Cons<A>(2*i+1,first), new Cons<A>(i*i, second));
            //the following line won't compile, it produces an error:
            //return _main(n-1, i+1, first, new Cons<A>(i*i, second));
        }
    }
}
public class Test{
    public static void main(String [] args){
        System.out.print("Enter a number: ");
        try {
            BufferedReader is = 
              new BufferedReader(new InputStreamReader(System.in));
            String line = is.readLine();
            Integer val = Integer.parseInt(line);
            System.out.println(_Test.main(val));
        } catch (NumberFormatException ex) {
            System.err.println("Not a valid number");
        } catch (IOException e) {
            System.err.println("Unexpected IO ERROR");
        }
    }
}

В C # вы можете написать почти то же самое. Попробуйте переписать его на C ++, и он не скомпилируется, жалуясь на бесконечное расширение шаблонов.

Автор: MigMit Размещён: 24.08.2013 04:12

2 плюса

262158 Репутация автора

Я подведу итоги в одном предложении: шаблоны создают новые типы, общие ограничения ограничивают существующие типы.

Автор: user207421 Размещён: 31.01.2016 02:43

1 плюс

529 Репутация автора

Ответ ниже взят из книги « Взлом кодовых решений для интервью» главы 13, которая, на мой взгляд, очень хорошая.

Реализация дженериков Java коренится в идее «стирания типов»: этот метод исключает параметризованные типы, когда исходный код транслируется в байт-код виртуальной машины Java (JVM). Например, предположим, что у вас есть код Java ниже:

Vector<String> vector = new Vector<String>();
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 не будет использовать статическую переменную совместно с MyClass. Однако экземпляры типа MyClass будут использовать статическую переменную.

/*** MyClass.h ***/
 template<class T> class MyClass {
 public:
 static int val;
 MyClass(int v) { val v;}
 };
 /*** MyClass.cpp ***/
 template<typename T>
 int MyClass<T>::bar;

 template class MyClass<Foo>;
 template class MyClass<Bar>;

 /*** main.cpp ***/
 MyClass<Foo> * fool
 MyClass<Foo> * foo2
 MyClass<Bar> * barl
 MyClass<Bar> * bar2

 new MyClass<Foo>(10);
 new MyClass<Foo>(15);
 new MyClass<Bar>(20);
 new MyClass<Bar>(35);
 int fl fool->val; // will equal 15
 int f2 foo2->val; // will equal 15
 int bl barl->val; // will equal 35
 int b2 bar2->val; // will equal 35

В Java статические переменные являются общими для всех экземпляров MyClass независимо от параметров другого типа.

Шаблоны Java и шаблоны C ++ имеют ряд других отличий. Они включают:

  • Шаблоны C ++ могут использовать примитивные типы, такие как int. Java не может и должна вместо этого использовать Integer.
  • В Java вы можете ограничить параметры типа шаблона определенным типом. Например, вы можете использовать обобщенные элементы для реализации CardDeck и указать, что параметр типа должен расширяться из CardGame.
  • В C ++ может быть создан экземпляр параметра типа, тогда как Java не поддерживает это.
  • В Java параметр типа (т. Е. Foo в MyClass) нельзя использовать для статических методов и переменных, поскольку они будут разделены между MyClass и MyClass. В C ++ эти классы различны, поэтому параметр типа может использоваться для статических методов и переменных.
  • В Java все экземпляры MyClass, независимо от параметров их типа, имеют одинаковый тип. Параметры типа стираются во время выполнения. В C ++ экземпляры с разными параметрами типов являются разными типами.
Автор: Jaycee Размещён: 01.07.2018 03:59
Вопросы из категории :
32x32