Создание легкого для поддержания конструктора копии

Рассмотрите следующий класс:

class A {

char *p;
int a, b, c, d;

public:
   A(const &A);
};

Обратите внимание, что я должен определить конструктора копии, чтобы сделать глубокую копию "p". Это имеет две проблемы:

  1. Большинство полей должно просто быть скопировано. Копирование их один за другим ужасно и подвержено ошибкам.

  2. Что еще более важно, каждый раз, когда новый атрибут добавляется к классу, конструктор копии должен быть обновлен, который создает кошмар обслуживания.

Я лично хотел бы сделать что-то как:

A(const A &a) : A(a)
{
   // do deep copy of p
   :::
}

Таким образом, конструктора копии по умолчанию называют первым, и затем глубокая копия выполняется.
К сожалению, это, кажется, не работает.

Там какой-либо лучший путь состоит в том, чтобы сделать это? Одно ограничение - я не могу использовать общие / интеллектуальные указатели.


Предложения Sbi имеют большой смысл. Я думаю, что пойду с созданием классов обертки для обработки ресурса. Я не хочу пользователю shared_ptr, так как библиотеки повышения не могут быть доступными на всех платформах (по крайней мере, не в стандартных дистрибутивах, OpenSolaris является примером).

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

Спасибо все для их ответов и полезной информации и извините об опечатках в моем вопросе. Я вводил его со своего телефона.

12
задан icedwater 19 September 2018 в 08:09
поделиться

9 ответов

Всегда используйте объекты RAII для управления неуправляемыми ресурсами, такими как необработанные указатели, и используйте ровно один объект RAII для каждого ресурса. В целом избегайте необработанных указателей. В этом случае лучшим решением будет использование std :: string .

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

3
ответ дан 2 December 2019 в 04:52
поделиться

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

Поместите этот char * в отдельный объект с соответствующим конструктором копирования и позвольте компилятору выполнить конструктор копирования для A . Обратите внимание, что это также касается присвоения и уничтожения , которые вы не упомянули в своем вопросе, но, тем не менее, должны быть рассмотрены.
В стандартной библиотеке для этого есть несколько типов, среди которых std :: string и std :: vector .

19
ответ дан 2 December 2019 в 04:52
поделиться

Вам действительно следует использовать здесь умные указатели.

Это позволит избежать перезаписи конструктора копирования и оператора изменения ( 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 предназначен только для заголовков (на основе шаблонов), поэтому они не требуют дополнительных зависимостей. (Иногда это имеет значение) Вы можете просто включить их, и все должно быть в порядке.

2
ответ дан 2 December 2019 в 04:52
поделиться

Вы можете выделить копируемые члены в 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.
}
3
ответ дан 2 December 2019 в 04:52
поделиться

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

Тем не менее, в этой статье описана реализация smart-указателя с семантикой глубокого копирования.

0
ответ дан 2 December 2019 в 04:52
поделиться

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

0
ответ дан 2 December 2019 в 04:52
поделиться

Если ваш класс не имеет одной функции, которая управляет ресурсом, вы никогда не должны управлять ресурсами напрямую. Всегда используйте умный указатель или пользовательский класс управления какого-либо описания. Как правило, лучше всего оставить неявный конструктор копирования, если это возможно. Такой подход также позволяет легко обслуживать деструктор и операторы присваивания.

0
ответ дан 2 December 2019 в 04:52
поделиться

Таким образом, сначала вызывается конструктор копирования по умолчанию, а затем выполняется глубокое копирование. К сожалению, это, похоже, не работает.

Есть ли лучший способ сделать это? Одно ограничение - я не могу использовать общие/интеллектуальные указатели.

Если я правильно понимаю, ваш вопрос, вы могли бы рассмотреть возможность использования функции инициализации:

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) )
{
}

Это имеет преимущество «эксплицитности» и легко отлаживается (вы можете вмешаться и посмотреть, что он делает для каждой инициализации).

0
ответ дан 2 December 2019 в 04:52
поделиться

Замените char * на std :: string .

4
ответ дан 2 December 2019 в 04:52
поделиться
Другие вопросы по тегам:

Похожие вопросы: