Должен ли конструктор / оператор / функция копирования выяснить, какой вариант копирования он реализует? - PullRequest
13 голосов
/ 28 июля 2010

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

Я написал много программного обеспечения на C ++, языке, который сильно зависит от копирования, и мне никогда не требовалось несколько вариантов копирования. Единственный вид операции копирования, который я когда-либо использовал, это тот, который я называю " достаточно глубокая копия ". Это делает следующее:

  • Если объект владеет переменной-членом (см. состав ), он копируется рекурсивно.
  • В случае, если объект не является владельцем переменной-члена (см. агрегация ), копируется только ссылка.

Теперь мой вопрос тройной:

  • 1) Требуется ли объекту более одного варианта копирования?
  • 2) Должна ли функция копирования указывать, какой вариант копирования она реализует?
  • 3) Кроме того, есть ли лучший термин для того, что я называю "достаточно глубокая копия"? Я задал связанный вопрос об определении термина «глубокая копия» .

Ответы [ 4 ]

5 голосов
/ 30 августа 2012

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

Если объект Foo содержит ссылку на объект исключительно с целью инкапсуляции неизменяемых аспектов, отличных от идентичности объекта, содержащегося в нем, то правильная копия Foo может содержать либо дубликат ссылки или ссылка на дубликат инкапсулированного объекта.

Если объект Foo содержит ссылку на объект исключительно с целью инкапсуляции изменяемых и неизменных аспектов объекта , отличных от идентичности , но никакая ссылка на этот объект никогда не будет открыта для чего-либо, что могло бы изменить его, применима та же ситуация.

Если объект Foo содержит ссылку на объект исключительно в целях инкапсуляции изменяемых и неизменных аспектов объекта , отличных от идентичности , и рассматриваемый объект будет видоизменяться, тогда правильный копия Foo должна содержать ссылку на дубликат инкапсулированного объекта.

Если объект Foo содержит ссылку на объект исключительно для инкапсуляции неизменяемых аспектов объекта , включая идентификатор , то правильная копия Foo должна содержать дубликат ссылка ; НЕ должно содержать ссылку на дублированный объект.

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

Единственный раз, когда имеет смысл говорить о «мелкой копии», это когда незавершенная операция используется в качестве одного из шагов при создании правильной копии. В противном случае существует только одна правильная «глубина» копии, управляемая типом состояния, инкапсулированным в ссылках на объект.

5 голосов
/ 28 июля 2010

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

Типы значений похожи на структуры.Они живут прямо в экземпляре объекта.Поэтому, когда вы копируете объект, у вас нет выбора, кроме как скопировать тип значения.Таким образом, вам, как правило, не нужно беспокоиться об этом.

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

Другой способ мышления состоит в том, что объект, переданный вашему объекту извне, вероятно, НЕ должен быть клонирован.Объект, сгенерированный вашим классом, должен быть.

Хорошо, я соврал, я буду использовать немного C ++, так как это лучше всего объяснит, что я имею в виду.

class MyClass {
    int foo;
    char * bar;
    char * baz;

public: MyClass(int f, char * str) {
        this->foo = f;
        bar = new char[f];
        this->baz = str;
    }
};

С этим объектом естьдва строковых буфера, с которыми нужно иметь дело.Первый, bar, создается и управляется самим классом.Когда вы клонируете объект, вы должны выделить новый буфер. С другой стороны,

baz не должно быть.На самом деле, вы не можете, так как у вас недостаточно информации для этого.Указатель должен быть просто скопирован.

И, конечно же, foo - это просто число.Просто скопируйте его, больше не о чем беспокоиться:)

В итоге, чтобы ответить на ваши вопросы напрямую:

  1. 99% времени, нет.Есть только один способ скопировать, который имеет смысл.Однако этот способ может быть разным.
  2. Не напрямую.Документировать это хорошая идея, но все внутреннее должно оставаться внутренним.
  3. Просто "Глубокая копия".Вы не должны (Edit: ALMOST) никогда не пытаться клонировать объект или указатель, который вы не контролируете, так что это освобождается от правил:)
2 голосов
/ 28 июля 2010

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

void f( std::string s );

нет способа сообщить компилятору, какскопируйте строку.

0 голосов
/ 01 апреля 2016

Немного поздний ответ, но в c ++ 11 вы более или менее охвачены:

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

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

...