Оператор = перегрузка в С++

В книге С++ 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