Оператор = перегрузка в С++
В книге С++ Primer у нее есть код для массивов символов C-стиля и показано, как перегрузить оператор =
в статье 15.3 Operator =.
String& String::operator=( const char *sobj )
{
// sobj is the null pointer,
if ( ! sobj ) {
_size = 0;
delete[] _string;
_string = 0;
}
else {
_size = strlen( sobj );
delete[] _string;
_string = new char[ _size + 1 ];
strcpy( _string, sobj );
}
return *this;
}
Теперь я хотел бы знать, почему существует необходимость вернуть ссылку String &
, когда этот код ниже выполняет одно и то же задание без каких-либо проблем:
void String::operator=( const char *sobj )
{
// sobj is the null pointer,
if ( ! sobj ) {
_size = 0;
delete[] _string;
_string = 0;
}
else {
_size = strlen( sobj );
delete[] _string;
_string = new char[ _size + 1 ];
strcpy( _string, sobj );
}
}
Ответы
Ответ 1
Он поддерживает следующую идиому:
String a, b;
const char *c;
// set c to something interesting
a = b = c;
Чтобы это сработало, b = c
должен вернуть соответствующий объект или ссылку для назначения a
; это фактически a = (b = c)
в соответствии с правилами приоритета оператора С++.
Если вы вернете указатель this
, вам нужно написать a = *(b = c)
, который не передает намеченного значения.
Ответ 2
@larsmans уже ответили на ваш точный вопрос, поэтому я действительно отвлекся: это какой-то дерьмовый код!
Проблема здесь в 3 раза:
- Вы просто продублировали код конструктора копирования (несколько)
-
strcpy
может быть лучше заменен на strncpy
, который выполняет некоторую проверку границ
- Не безопасно
1) и 2) более стилистичны, чем что-либо, но 3) является большой проблемой
EDIT:, как указано @Jerry Coffin, это не защищено от самонаправления. То есть, если sobj
и _string
указывают на один и тот же массив символов, у вас большие проблемы. Легкое решение в конце этого поста также охватывает эту ситуацию.
Безопасное исключение
Посмотрим на часть кода, а именно на часть else
:
_size = strlen( sobj );
delete[] _string;
_string = new char[ _size + 1 ];
strcpy( _string, sobj );
Что произойдет, если по какой-то причине new
выбрасывает?
-
_size
имеет значение новой строки
-
_string
указывает на старый указатель... который был освобожден
Следовательно, не только объект остается в непригодном состоянии (половина его данных от нового объекта, половина от старого), но его нельзя даже уничтожить (если только деструктор не протекает...?)
Добавление безопасности исключений, трудный путь
_size = strlen( sobj );
delete[] _string;
try {
_string = new char[ _size + 1 ];
} catch(...) {
_size = 0; _string = 0;
throw;
}
strcpy( _string, sobj );
Хорошо, это правда, но это приносит нам гарантию Basic Exception: никакой функциональной гарантии, а гарантия того, что код технически корректен (без сбоев, без утечки).
Добавление безопасности исключений, простой способ: Идиома Copy-And-Swap
Найдите более полное описание по адресу: Что такое идиома копирования и замены?
void swap(String& lhs, String& rhs) {
using std::swap;
swap(lhs._size, rhs._size);
swap(lhs._string, rhs._string);
}
String& String::operator=(String other) { // pass-by-value
swap(*this, other);
return *this;
}
Как это работает?
- мы повторно используем конструктор копирования для фактической копии
- мы повторно используем функцию подкачки для обмена значениями
- мы повторно используем деструктор для очистки
И это даже лучше, чем предыдущая версия, на данный момент у нас есть гарантия Strong Exception: она транзакционная, поэтому, если она терпит неудачу, строка, которую мы назначили, не изменилась (как будто ничего не произошло).
Подробнее о гарантиях на исключение.
Я немного обескуражен тем, что учебник С++ будет продвигать такой сомнительный код, молись сказать мне пример того, что не делать:/
Ответ 3
Конвенция. Встроенные типы делают это, автоматически создаваемые операторы присваивания делают это, это позволяет вам сделать это:
a = b = c;
или это:
if ( foo = come_function() ) {
// use foo here
}
Ответ 4
Возвращение из оператора = - это значит, что вы можете связать вещи. Смотрите здесь без строк:
x = y = 2;
Часть y=2
этой строки возвращает 2. Как x получает значение. Если op = return void, вы не можете цепью = операции.
Ответ 5
По соглашению вы можете сделать, например:
int x, y, z;
x = y = z = 0;
if ((x = 1) != 0) { ... }
Чтобы сохранить эту семантику для экземпляров вашего класса, вам нужно вернуть *this
- разрешая прикованное присвоение. Если вы вернете только this
, вы вернете указатель на свой экземпляр, а цепочка присваивания не будет работать - нет operator=
для String *
, есть один для String