2013-03-12 3 views
1

У меня есть класс A, который динамически выделяет память для целого числа (указывается memmber класса say _pPtrMem) в своем конструкторе и освобождает его в деструкторе. Чтобы избежать мелкой копии, я перегрузил оператор присваивания и конструктор копирования. Широко используемый способ, в котором перегружен оператор присваивания следующим образом:Лучший способ перегрузить оператор присваивания C++

A& operator = (const A & iToAssign) 
{ 
    if (this == & iToAssign) // Check for self assignment 
    return *this; 
    int * pTemp = new int(*(iToAssign._pPtrMem)); // Allocate new memory with same value 
    if (pTemp) 
    { 
    delete _pPtrMem; // Delete the old memory 
    _pPtrMem = pTemp; // Assign the newly allocated memory 
    } 
    return *this; // Return the reference to object for chaining(a = b = c) 
} 

Другой способ реализации такой же может быть

A& operator = (const A & iToAssign) 
{ 
    *_pPtrMem= *(iToAssign._pPtrMem); // Just copy the values 
    return *this; 
} 

Поскольку вторая версия сравнительно гораздо проще и быстрее (не открепление, распределение памяти), почему он не используется широко? Любые проблемы, которые я не могу разобрать?

Также мы возвращаем объект того же типа от оператора присваивания для построения цепочки (а = Ь = с) ... так что вместо того, чтобы вернуться * это это прекрасно, чтобы вернуть iToAssign объект как оба объекта предположительно теперь равны?

+2

Если у вас всего одно целое, зачем использовать указатели? Если у вас есть динамически распределенные массивы, используйте вместо этого 'std :: vector'. –

+1

Примечание: в версии 1 'delete' слишком рано. Это должно быть сделано после того, как текущий объект находится в стабильном состоянии (в противном случае вы не предоставляете надежную гарантию исключения). Здесь он нестабилен, потому что вы назначаете текущий объект после удаления. Вы должны «std :: swap (_pPtrMem, pTemp); удалить pTemp;'. Еще лучше использовать идиому копирования и подкачки. –

+0

@LokiAstari, но назначение указателей не может вызвать исключение, нет ничего, что может пойти не так. –

ответ

1

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

0

Во втором случае, если _pPtrMem первоначально был неназначенным, линия

*_pPtrMem= *(iToAssign._pPtrMem); // Just copy the values 

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

+1

* "У меня есть класс A, который динамически выделяет [...]' pPtrMem' [..] в своем конструкторе "*. – Zeta

6

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

A& A::operator= (A iToAssign) // note pass by value here - will invoke copy constructor 
{ 
    iToAssign.swap(*this); 
    return *this; 
} 
// void swap(A& other) throws() // C++03 
void A::swap(A& other) noexcept 
{ 
    std::swap(_pPtrMem, other._pPtrMem); 
} 

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

+2

* "или используйте стандартный, если он делает то, что вы хотите" * - Мне кажется, что это приведет к бесконечной рекурсии, поскольку 'std :: swap' полагается на' operator = '. –

+0

@LokiAstari Это не то же самое. Если вы предоставляете свою собственную перегрузку 'swap()' для своего класса, то идион 'using std :: swap' позволяет найти эту перегрузку через ADL, и только если такой вещи не существует, используется' 'using'-ed ' 'std :: swap'. – Angew

+0

@Angew: Да, понял. Но вы не можете использовать std :: swap в операторе присваивания (согласно комментарию Benjamins). Таким образом, вы должны определить свой собственный (или выполнить обмен вручную в задании). Таким образом, снова нет смысла вводить оператор using. –

1

Нет, никаких проблем с вашей реализацией нет. Но наличие одного целочисленного динамического выделения по крайней мере очень особенное.

Эта реализация широко не используется, поскольку никто не выделяет единственное целое число в свободном хранилище. Обычно вы используете динамическую выделенную память для массивов с переменной длиной, неизвестной во время компиляции. И в этом случае большую часть времени неплохо использовать std :: vector.

Нет, это не нормально, чтобы вернуть другой объект. Идентичность - это не то же самое, что равенство:

T a, b, d; 
T& c = a = b; 
c = d; // should change a, not b 

Ожидаете ли вы, что третья строка изменится?

Или событие лучше Пример:

T a; 
T& b = a = T(); 

Это приведет к висячей ссылке, ссылка на временное и разрушенное производственном объект.

+0

Спасибо за четкое объяснение моего второго запроса ... отличные примеры! – Arun

0

В этом случае вторая реализация намного лучше. Но обычная причина использования динамического объекта в том, что размер может отличаться. (Еще одна причины в том, что вы хотите в эталонной семантики мелкой копии.) В таких случаях простое решение является использовать первое задание (без теста для самостоятельного присвоения). В зависимости от объекта, возможно, рассмотрит возможность повторного использования уже имеющейся памяти, если новое значение будет соответствовать; это добавляет сложности несколько (так как вы должны проверить, будет ли он соответствовать, и по-прежнему делать распределение/копировать/удалять, если это не так), но в некоторых случаях он может улучшить производительность .

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