Что не так с конструкторами копирования? Зачем использовать интерфейс 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().) В С++ это не проблема: вы можете передать весь объект или его ссылку.