Создание простого в обслуживании конструктора копирования - PullRequest
12 голосов
/ 07 июля 2010

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

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

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

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

Ответы [ 9 ]

19 голосов
/ 07 июля 2010

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

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

4 голосов
/ 07 июля 2010

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

3 голосов
/ 07 июля 2010

Вы можете разделить своих копируемых участников в POD-структуру и обслуживать своих участников, которым требуется управляемая копия отдельно.

Поскольку ваши члены данных являются частными, они могут быть невидимы для клиентов вашего класса.

* 1005 Е.Г. *

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 голосов
/ 07 июля 2010

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

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

2 голосов
/ 07 июля 2010

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

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

0 голосов
/ 07 июля 2010

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

0 голосов
/ 07 июля 2010

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

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

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

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, я все же предпочитаю создавать конструктор копирования и использовать списки инициализаторов для каждогоmember (на самом деле, я предпочитаю делать это даже при использовании 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 голосов
/ 07 июля 2010

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

0 голосов
/ 07 июля 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...