Подтвердить что ты не робот

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

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

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, поскольку библиотеки boost могут быть недоступны на всех платформах (по крайней мере, не в стандартных дистрибутивах, OpenSolaris - пример).

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

Спасибо всем за их ответы и полезную информацию и жаль о опечатках в моем вопросе. Я печатал его со своего телефона.

4b9b3361

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

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

Ответ 4

Вы можете разделить свои скребующиеся элементы в 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.
}

Ответ 5

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

Это позволит избежать переписывания как конструктора копирования, так и оператора адаптации (operator=).

Оба из них подвержены ошибкам.

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

Ответ 6

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

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

Ответ 7

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

Ответ 8

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

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

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

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

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

Ответ 9

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