Рассмотрите следующий класс:
class A {
char *p;
int a, b, c, d;
public:
A(const &A);
};
Обратите внимание, что я должен определить конструктора копии, чтобы сделать глубокую копию "p". Это имеет две проблемы:
Большинство полей должно просто быть скопировано. Копирование их один за другим ужасно и подвержено ошибкам.
Что еще более важно, каждый раз, когда новый атрибут добавляется к классу, конструктор копии должен быть обновлен, который создает кошмар обслуживания.
Я лично хотел бы сделать что-то как:
A(const A &a) : A(a)
{
// do deep copy of p
:::
}
Таким образом, конструктора копии по умолчанию называют первым, и затем глубокая копия выполняется.
К сожалению, это, кажется, не работает.
Там какой-либо лучший путь состоит в том, чтобы сделать это? Одно ограничение - я не могу использовать общие / интеллектуальные указатели.
Предложения Sbi имеют большой смысл. Я думаю, что пойду с созданием классов обертки для обработки ресурса. Я не хочу пользователю shared_ptr, так как библиотеки повышения не могут быть доступными на всех платформах (по крайней мере, не в стандартных дистрибутивах, OpenSolaris является примером).
Я все еще думаю, что было бы замечательно, если Вы могли бы так или иначе сделать компилятор для создания конструктора по умолчанию / операторы присваивания для Вас, и Вы могли просто добавить свою функциональность сверху его. Вручную созданный конструктор/оператор присваивания копии функционирует, я думаю, будет стычка для создания и кошмар для поддержания. Таким образом, мое персональное эмпирическое правило состояло бы в том, чтобы избежать пользовательских конструкторов/операторов присваивания копии по всей стоимости.
Спасибо все для их ответов и полезной информации и извините об опечатках в моем вопросе. Я вводил его со своего телефона.
Всегда используйте объекты RAII для управления неуправляемыми ресурсами, такими как необработанные указатели, и используйте ровно один объект RAII для каждого ресурса. В целом избегайте необработанных указателей. В этом случае лучшим решением будет использование std :: string
.
Если это невозможно по какой-либо причине, учитывайте легкость копирования частей в базовый класс или объект-член.
Практическое правило: Если вам нужно вручную управлять ресурсами, поместите каждый в отдельный объект.
Поместите этот char *
в отдельный объект с соответствующим конструктором копирования и позвольте компилятору выполнить конструктор копирования для A
. Обратите внимание, что это также касается присвоения и уничтожения , которые вы не упомянули в своем вопросе, но, тем не менее, должны быть рассмотрены.
В стандартной библиотеке для этого есть несколько типов, среди которых std :: string
и std :: vector
.
Вам действительно следует использовать здесь умные указатели.
Это позволит избежать перезаписи конструктора копирования и оператора изменения ( operator =
).
Оба они подвержены ошибкам.
Типичная ошибка с operator =
реализует его таким образом:
SomeClass& operator=(const SomeClass& b)
{
delete this->pointer;
this->pointer = new char(*b.pointer); // What if &b == this or if new throws ?
return *this;
}
Что не удается, когда это происходит:
SomeClass a;
a = a; // This will crash :)
Интеллектуальные указатели уже обрабатывают эти случаи и, очевидно, меньше подвержены ошибкам.
Более того, интеллектуальные указатели, такие как boost :: shared_ptr
, могут даже обрабатывать пользовательскую функцию освобождения памяти (по умолчанию она использует delete
). На практике я редко сталкивался с ситуацией, когда использование интеллектуального указателя вместо необработанного указателя было непрактичным.
Небольшое примечание: класс интеллектуальных указателей boost
предназначен только для заголовков (на основе шаблонов), поэтому они не требуют дополнительных зависимостей. (Иногда это имеет значение) Вы можете просто включить их, и все должно быть в порядке.
Вы можете выделить копируемые члены в POD-структуру и хранить члены, требующие управляемой копии, отдельно.
Поскольку ваши члены данных являются приватными, это может быть незаметно для клиентов вашего класса.
Например,
class A {
char *p;
struct POData {
int a, b, c, d;
// other copyable members
} data;
public:
A(const &A);
};
A(const A& a)
: data( a.data )
{
p = DuplicateString( a.p );
// other managed copies...
// careful exception safe implementation, etc.
}
Вопрос в том, действительно ли вам нужен указатель с семантикой глубокого копирования в вашем классе? По моему опыту, ответ почти всегда отрицательный. Возможно, вы могли бы объяснить свой сценарий, чтобы мы могли показать вам альтернативные решения.
Тем не менее, в этой статье описана реализация smart-указателя с семантикой глубокого копирования.
Хотя я согласен с другими, которые говорят, что вы должны заключить указатель в отдельный класс для RAII и позволить компилятору синтезировать конструктор копирования, деструктор и оператор присваивания, есть способ обойти вашу проблему: объявить (и определить) частную статическую функцию который будет делать все необходимое и общее для разных конструкторов и вызывать его оттуда.
Если ваш класс не имеет одной функции, которая управляет ресурсом, вы никогда не должны управлять ресурсами напрямую. Всегда используйте умный указатель или пользовательский класс управления какого-либо описания. Как правило, лучше всего оставить неявный конструктор копирования, если это возможно. Такой подход также позволяет легко обслуживать деструктор и операторы присваивания.
Таким образом, сначала вызывается конструктор копирования по умолчанию, а затем выполняется глубокое копирование. К сожалению, это, похоже, не работает.
Есть ли лучший способ сделать это? Одно ограничение - я не могу использовать общие/интеллектуальные указатели.
Если я правильно понимаю, ваш вопрос, вы могли бы рассмотреть возможность использования функции инициализации:
class A
{
int i, j;
char* p;
void Copy(int ii, int jj, char* pp); // assign the values to memebers of A
public:
A(int i, int j, char* p);
A(const A& a);
};
A::A(int i, int j, char* p)
{
Copy(i, j, p);
}
A::A(const A& a)
{
Copy(a.i, a.j, a.p);
}
Тем не менее, вам действительно следует рассмотреть возможность использования RAII (есть причина, по которой люди продолжают рекомендовать его :)) для ваших дополнительных ресурсов.
Если я не могу использовать RAII, я все равно предпочитаю создавать конструктор копирования и использовать списки инициализаторов для каждого члена (на самом деле, я предпочитаю делать это даже при использовании RAII):
A::A(int ii, int lj, char* pp)
: i(ii)
, j(jj)
, p( function_that_creates_deep_copy(pp) )
{
}
A::A(const A& a)
: i(a.i)
, j(a.j)
, p( function_that_creates_deep_copy(a.p) )
{
}
Это имеет преимущество «эксплицитности» и легко отлаживается (вы можете вмешаться и посмотреть, что он делает для каждой инициализации).