Что не так с конструкторами копирования? Зачем использовать Cloneable интерфейс? - PullRequest
13 голосов
/ 23 декабря 2008

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

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

Ответы [ 6 ]

18 голосов
/ 23 декабря 2008

Я думаю, это потому, что нет необходимости в конструкторе копирования в Java и в C # для ссылочных типов. В C ++ объекты названы. Вы можете (и вы будете чаще всего) копировать (и в C ++ 1x перемещать) их, например, при возврате из функций, поскольку для возврата указателей требуется выделение динамической памяти, управление которой было бы медленным и болезненным. Синтаксис T (x), поэтому имеет смысл создать конструктор, используя ссылку на T. C ++ не может создать функцию клонирования, поскольку для этого потребуется снова вернуть объект по значению (и, следовательно, другую копию).

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

4 голосов
/ 23 декабря 2008

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

В Java и C # присваивание просто включает копирование дескриптора во внутренний объект. В основном, когда вы видите:

SomeClass x = new SomeClass();

в Java или C #, есть встроенный уровень косвенности, которого нет в C ++. В C ++ вы пишете:

SomeClass* x = new SomeClass();

Назначение в C ++ включает разыменованное значение:

*x = *another_x;

В Java вы можете получить доступ к «реальному» объекту, так как отсутствует оператор разыменования, такой как * x. Итак, чтобы сделать глубокое копирование, вам нужна функция: clone (). И Java, и C # обернули эту функцию в интерфейс.

3 голосов
/ 23 декабря 2008

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

Я настоятельно рекомендую вам тщательно изучить клонирование, какой бы путь вы ни выбрали - вы, вероятно, выберете опцию clone (), но убедитесь, что вы точно знаете, как это сделать правильно. Это скорее похоже на equals () и hashCode () - на первый взгляд выглядит просто, но это должно быть сделано правильно.

2 голосов
/ 19 декабря 2009

Я думаю, вы не поняли правильную точку. Я даю тебе свои два цента.

По сути, есть проблема: создание клона класса без знания точного типа класса. Если вы используете конструктор копирования, вы не сможете.

Вот пример:

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, если объявлен виртуальным, может создать правильный экземпляр класса универсального члена.

Эта проблема является общей для всех языков, C # C ++ или Java ...

Может быть, это то, что вы имели в виду, но я не могу понять это из любого ответа.

1 голос
/ 02 апреля 2011

Просто хотел добавить, что в Java конструктор копирования не совсем бесполезен.

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

Защитная копия может быть реализована путем вызова clone() (класс Date является клонируемым), но злоумышленник может вызвать сеттер с подклассом Date, который переопределяет метод clone() с помощью {return this;} и поэтому вызывающий может все еще иметь возможность манипулировать вашим объектом. Вот где в игру вступает конструктор копирования: вызывая new Date(theDate), вы обязательно получите свежий экземпляр Date с той же отметкой времени, что и указанная дата, без какой-либо связи между двумя экземплярами даты. В геттере вы можете использовать метод clone, потому что вы знаете, что закрытая переменная будет иметь класс Date, но для согласованности обычно используется и конструктор копирования.

Также обратите внимание, что конструктор копирования отметил бы, что требуется, если класс Date был конечным (вызов clone() безопасен) или неизменным (копирование не требуется).

0 голосов
/ 11 января 2010

Я думаю, это только потому, что, как только вы определили конструктор копирования, вы никогда не сможете снова передать саму ссылку. (Если у него не было бы функции, которая делает это ... но это не легче, чем использование метода clone ().) В C ++ это не проблема: вы можете передать весь объект или его ссылку.

...