Что не так с конструкторами копирования? Зачем использовать интерфейс Cloneable?
При программировании С++ мы при необходимости использовали конструкторы копирования (или нас учили). При переходе на Java несколько лет назад я заметил, что теперь используется интерфейс Cloneable. С# соответствует тому же маршруту, определяющему интерфейс ICloneable. Мне кажется, что клонирование является частью определения ООП. Но мне интересно, почему были созданы эти интерфейсы, и конструктор копирования, похоже, был удален?
Когда я подумал об этом, я пришел к мысли, что конструктор копирования не будет полезен, если нужно сделать копию объекта, тип которого неизвестен (как в случае ссылки на базовый тип). Это кажется логичным. Но мне интересно, есть ли другие причины, о которых я не знаю, для которых интерфейсы Cloneable были предпочтительнее конструкторов копирования?
Ответы
Ответ 1
Я думаю, это потому, что нет такой неотъемлемой потребности в конструкторе копирования в Java и в С# для ссылочных типов. В С++ объекты называются. Вы можете (и чаще всего) копировать (и в С++ 1x перемещать) их, например, при возврате из функций, поскольку возвращающие указатели требуют, чтобы вы выделяли динамическую память, которая была бы медленной и болезненной для управления. Синтаксис T (x), поэтому имеет смысл сделать конструктор, берущий T-ссылку. С++ не смог создать функцию клонирования, поскольку для этого потребуется снова вернуть объект по значению (и, следовательно, еще одну копию).
Но в Java объекты неназванные. Есть только ссылки на них, которые можно скопировать, но сам объект не копируется. Для случаев, когда вам действительно нужно их скопировать, вы можете использовать вызов clone (но я читаю в другом anwers, клон ошибочен. Я не программист на Java, поэтому я не могу прокомментировать это). Поскольку не возвращается сам объект, а скорее ссылка на него, достаточно будет использовать функцию клонирования. Кроме того, функция клонирования может быть переопределена. Это не будет работать с конструкторами копирования. И, кстати, в С++, когда вам нужно скопировать полиморфный объект, требуется также функция clone
. Он получил имя, так называемый виртуальный экземпляр копии.
Ответ 2
Потому что С++ и Java (и С#) - это не одно и то же. С++ не имеет встроенных интерфейсов, поскольку интерфейсы не являются частью языка. Вы можете подделать их абстрактными классами, но они не так, как вы думаете о С++. Кроме того, в С++ назначение обычно глубокое.
При назначении Java и С# требуется только копирование дескриптора во внутренний объект. В основном, когда вы видите:
SomeClass x = new SomeClass();
в Java или С#, существует уровень встроенной функции косвенности, которая не существует в С++. В С++ вы пишете:
SomeClass* x = new SomeClass();
Назначение в С++ включает в себя разыменованное значение:
*x = *another_x;
В Java вы можете получить доступ к "реальному" объекту, так как нет оператора разыменования, такого как * x. Чтобы сделать глубокую копию, вам нужна функция: clone(). И как Java, так и С# обернули эту функцию в интерфейс.
Ответ 3
Это проблемы конечного типа и каскадирования операции клонирования через суперклассы, которые не рассматриваются конструкторами копирования - они не расширяемы. Но механизм клонирования Java широко считается также сильно нарушенным; особенно проблемы, когда подкласс не реализует clone(), но наследует от суперкласса, который реализует клонируемые.
Я настоятельно рекомендую вам тщательно исследовать клонирование, независимо от выбранного вами пути - вы, скорее всего, выберете вариант clone(), но убедитесь, что вы точно знаете, как это сделать. Это скорее похоже на equals() и hashCode() - выглядит просто на поверхности, но это нужно делать точно.
Ответ 4
Я думаю, что вы не получили правильной точки. Я даю вам свои два цента.
В принципе возникает проблема: создание клона класса без знания точного типа класса. Если вы используете конструктор копирования, вы не можете.
Вот пример:
class A {
public A(A c) { aMember = c.aMember }
int aMember;
}
class B : A {
public B(B c) : base(c) { bMember = c.bMember }
int bMember;
}
class GenericContainer {
public GenericContainer(GenericContainer c) {
// XXX Wrong code: if aBaseClass is an instance of B, the cloned member won't
// be a B instance!
aBaseClass = new A(c.aBaseClass);
}
A aBaseClass;
}
Метод Clone, если объявить виртуальным, может создать правильный экземпляр класса для общего элемента.
Эта проблема является общей для каждого языка, С# С++ или Java...
Возможно, это то, что вы имели в виду, но я не могу понять это из любого ответа.
Ответ 5
Просто хотел добавить, что в Java конструктор копирования не совсем бесполезен.
Бывают случаи, когда ваш класс имеет переменную private частного экземпляра изменяемого нефинального типа, например. Date
, и у него есть сеттер и получатель для переменной. В установщике вы должны сделать копию данной даты, потому что вызывающий может позднее ее модифицировать и тем самым манипулировать внутренним состоянием вашего объекта (обычно случайно, но, возможно, намеренным). В газопоглотителе требуется такая же мера предосторожности.
Защитная копия может быть реализована путем вызова clone()
(класс Date
является клонированным), но злоумышленник может вызвать установщик с подклассом Date
, который переопределяет метод clone()
с помощью {return this;}
, и, таким образом, вызывающий может все еще иметь возможность манипулировать вашим объектом. Здесь вступает в действие конструктор копирования: вызывая new Date(theDate)
, вы обязательно получите свежий экземпляр Date
с той же меткой времени, что и указанная дата, без какой-либо связи между двумя экземплярами даты. В getter вы можете использовать метод clone, потому что знаете, что личная переменная будет иметь класс Date
, но для согласованности обычно используется и конструктор копирования.
Также обратите внимание, что конструктор копирования будет заметен, если класс Date
был окончательным (вызов clone()
был безопасным) или неизменяемым (без копирования).
Ответ 6
Я думаю, что это только потому, что как только вы определили конструктор копирования, вы больше никогда не сможете передать эту ссылку. (Если бы у него не было функции, которая делает это... но это не проще, чем использование метода clone().)
В С++ это не проблема: вы можете передать весь объект или его ссылку.