Должен ли конструктор/оператор/функция копирования указать, какой вариант копирования он реализует?
Вчера я спросил вопрос о копировании объектов на С#, и большинство ответов было сосредоточено на различии между глубокой копией и мелкой copy, и тот факт, что должно быть ясно, какой из обоих вариантов копирования реализует данный конструктор копирования (или оператор или функция). Я нахожу это странным.
Я написал много программного обеспечения на С++, язык, который сильно зависит от копирования, и мне никогда не требовалось несколько вариантов копирования. Единственная операция копирования, которую я когда-либо использовал, - это тот, который я называю " достаточно глубокой копией". Он выполняет следующие действия:
- Если объект имеет право собственности на переменную-член (cf. composition), он копируется рекурсивно.
- Если объект не имеет права собственности на переменную-член (см. aggregation), копируется только ссылка.
Теперь мой вопрос трижды:
- 1) Требуется ли когда-нибудь объект более одного варианта копирования?
- 2) Требуется ли функция копирования для определения того, какой вариант копирования он реализует?
- 3) В стороне, есть ли лучший термин для того, что я называю "достаточно глубокой копией"? Я задал вопрос об определении термина "глубокая копия" .
Ответы
Ответ 1
Объекту нужно только скопировать то, что ему нужно скопировать. Хотя этот вопрос отмечен как язык агностик, и вы упомянули С++, я предпочитаю объяснять в терминах С# (так как это то, что я больше всего знаю). Однако концепции схожи.
Типы значений похожи на структуры. Они живут непосредственно в экземпляре объекта. Поэтому при копировании объекта у вас нет выбора, кроме как скопировать тип значения. Таким образом, вам вообще не нужно беспокоиться об этом.
Ссылочные типы похожи на указатели, и это становится сложной задачей. В зависимости от типа ссылочного типа вы можете или не хотите иметь глубокую копию. Общее правило состоит в том, что если ссылочный тип (как член объекта) зависит от состояния внешнего объекта, он должен быть клонирован. Если нет, и никогда не будет, это не обязательно.
Другим способом мышления является то, что объект, переданный вашему объекту снаружи, вероятно, НЕ должен быть клонирован. Объект, сгенерированный вашим классом, должен быть.
Хорошо, я соврал, я буду использовать некоторый С++, потому что это лучше всего объяснит, что я имею в виду.
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
- это просто число. Просто скопируйте его, больше не о чем беспокоиться:)
В заключение, чтобы ответить на ваши вопросы напрямую:
- 99% времени, нет. Там только один способ скопировать это имеет смысл. Однако этот способ меняется.
- Не напрямую. Документирование это хорошая идея, но что-то внутреннее должно оставаться внутренним.
- Просто "Deep copy". Вы должны (Edit: ALMOST) никогда не пытаться клонировать объект или указатель, который вы не контролируете, чтобы освободить от правил:)
Ответ 2
Различие между "глубокой копией" и "мелкой копией" имеет смысл в качестве детали реализации, но позволяет ей просачиваться дальше, что обычно указывает на недостаток абстракции, который, скорее всего, проявится и другими способами.
Если объект Foo
содержит ссылку на объект исключительно для инкапсуляции неизменяемых аспектов, кроме идентификатора, содержащегося в нем объекта, то правильная копия Foo
может либо содержать дубликат ссылки или ссылки на дубликат инкапсулированного объекта.
Если объект Foo
содержит ссылку на объект исключительно для инкапсуляции изменяемых и неизменных аспектов объекта , отличного от идентификатора, но ссылка на этот объект никогда не будет подвергаться действию чего-либо, что будет мутировать его, применяется такая же ситуация.
Если объект Foo
содержит ссылку на объект исключительно для целей инкапсуляции изменяемых и неизменных аспектов объекта , отличного от идентификатора, и объект, о котором идет речь, будет мутирован, тогда правильная копия Foo
должна содержать ссылку на дубликат инкапсулированного объекта.
Если объект Foo
содержит ссылку на объект исключительно для инкапсуляции неизменных аспектов объекта , включая идентификатор, то правильная копия Foo
должна содержать дубликат ссылка; он должен NOT содержать ссылку на дублированный объект.
Если объект Foo
содержит ссылку на объект для инкапсуляции как изменчивого состояния, так и объекта, то невозможно создать правильную копию Foo
по отдельности. Правильная копия Foo
может быть создана только путем дублирования всего набора объектов, к которым он прикреплен.
Единственный раз, когда имеет смысл говорить о "мелкой копии", это когда неполная операция используется как один из шагов при создании правильной копии. В противном случае существует только одна правильная копия "глубина", контролируемая типом состояния, инкапсулированным в ссылках на объекты.
Ответ 3
Большинство программистов на C++ не используют термины "мелкая копия" и "глубокая копия" по очень хорошей причине, что обычно существует только один способ копирования объекта. Это особенно верно в С++, потому что компилятор использует конструктор копирования во многих ситуациях, когда программист мог сказать, какой конструктор копирования использовать - например:
void f( std::string s );
нет способа сообщить компилятору, как скопировать строку.
Ответ 4
Немного позднего ответа, но С++ 11 у вас более или менее охвачено:
Решение, как подробно описано в этом ответе Какой тип указателя я использую, когда?, используйте разные типы указателей, чтобы выразить свое (совместное) право собственности.
Поскольку std::unique_ptr
не копируется, вы будете вынуждены сделать копию данных, принадлежащих уникальному указателю. Утверждение всего в части членства в членстве, вероятно, всегда будет ясно, какую копию использовать для какого члена.