2010-07-07 1 views
12

Рассмотрим следующий класс:Создание легко поддерживать конструктор копирования

class A { 

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

public: 
    A(const &A); 
}; 

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

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

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

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

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

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

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


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

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

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

+5

Почему вы не можете использовать интеллектуальные указатели? – 2010-07-07 07:34:56

+4

@Neil: Возможно, у его клавиатуры нет подчеркивания? – fredoverflow

+0

Все ответы до сих пор сосредоточены на том, что вам не нужна специальная копия c'tor. Но вопросительная часть вопроса, поднятая здесь, заключается в следующем: возможно ли сохранить некоторые автоматически создаваемые функциональные возможности и просто расширить его (не ограничиваясь только указанием на указатель). –

ответ

4

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

+2

Я думаю, что char * p является просто примером для любого указателя в классе, нуждающемся в специальном лечении в копии c'tor. –

+2

@ Gene Vincent: Тогда OP должен сказать это в вопросе. В этом случае для управления указателем должен быть написан специальный класс RAII. – Philipp

+2

@Philipp: ... или подходящий готовый вариант. – sbi

3

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

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

2

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

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

+1

Использование 'shared_ptr' здесь означает, что все копии этого объекта имеют один и тот же экземпляр объекта, на который указывает. Возможно, это не то, чего хочет OP. –

+0

@ Space_C0wb0y, вы абсолютно правы. Однако, даже если он хочет, чтобы у каждого экземпляра была своя собственная копия, использование интеллектуальных указателей все же гораздо меньше подвержено ошибкам. – ereOn

+2

'this.pointer' ?? Для меня это не похоже на C++. Объяснение также пропустит еще одну ключевую концепцию C++, безопасность исключений. Код, скорее всего, сработает, если 'new' выкинет, а' this-> pointer' будет удален во второй раз в dtor. – MSalters

19

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

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

+3

+1 Никогда не забывайте о большой тройке: http://en.wikipedia.org/wiki/Rule_of_three_%28C%2B%2B_programming%29 –

+3

Это совет следовать. Всякий раз, когда вам нужно написать конструктор копий для «функционального» класса (в отличие от технического), вы, вероятно, делаете что-то неправильно. –

0

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

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

3

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

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

E.g.

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. 
} 
0

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

0

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

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

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

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

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

Смежные вопросы