Подтвердить что ты не робот

Что не так с "проверкой самонаведения" и что это значит?

В книге Херба Саттера Исключительный С++ (1999) у него есть слова в решении пункта 10:

"Исключение - небезопасное" и "плохой дизайн" идут рука об руку. Если кусок кода не является безопасным для исключений, это обычно хорошо и может быть просто исправлено. Но если часть кода не может быть исключена из-за своей базовой конструкции, это почти всегда является сигналом ее плохого дизайна.

Пример 1: Функция с двумя различными обязанностями сложна для исключения исключений.

Пример 2: Оператор присваивания копий, который написан таким образом, что он должен проверять самоопределение, вероятно, не является безопасным с точки зрения безопасности

Что он подразумевает под термином "проверка на самоназвание" ?

[ЗАПРОС]

Дэйв и АндрейТ показывают нам, что означает "проверка на самоназвание" . Это хорошо. Но вопрос еще не закончен. Почему "проверка на самоназвание" вредит "безопасности исключений" (согласно Hurb Sutter)? Если вызывающий пытается выполнить самоопределение, то "проверка" работает так, как будто никакого назначения когда-либо не происходит. Это действительно больно?

[MEMO 1] В пункте 38 "Идентификация объекта" позже в книге "Херб" он объясняет самоназначение.

4b9b3361

Ответ 1

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

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

Например, если бы я хотел реализовать упрощенный класс массива в строках

class array {
  ...
  int *data;
  size_t n;
};

и придумал следующую реализацию оператора присваивания

array &array::operator =(const array &rhs) 
{
  delete[] data;

  n = rhs.n;
  data = new int[n];
  std::copy_n(rhs.data, n, data);

  return *this;
}

что реализация будет считаться "плохим", поскольку она явно терпит неудачу в случае самонаправления.

Чтобы "исправить", можно либо добавить явную проверку самоопределения

array &array::operator =(const array &rhs) 
{
  if (&rhs != this) 
  {
    delete[] data;

    n = rhs.n;
    data = new int[n];
    std::copy_n(rhs.data, n, data);
  }

  return *this;
}

или следовать методу "без учета"

array &array::operator =(const array &rhs) 
{
  size_t new_n = rhs.n;
  int *new_data = new int[new_n];
  std::copy_n(rhs.data, new_n, new_data);

  delete[] data;

  n = new_n;
  data = new_data;

  return *this;
}

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

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

Ответ 2

MyClass& MyClass::operator=(const MyClass& other)  // copy assignment operator
{
    if(this != &other) // <-- self assignment check
    {
        // copy some stuff
    }

    return *this;
}

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

Ответ 3

Из справочника ядра С++

Foo& Foo::operator=(const Foo& a)   // OK, but there is a cost
{
    if (this == &a) return *this;
    s = a.s;
    i = a.i;
    return *this;
}

Это очевидно безопасно и, по-видимому, эффективно. Однако что делать, если мы выполняем одно задание на миллионы присваиваний? Это около миллиона избыточных тестов (но поскольку ответ, по существу, всегда один и тот же, предсказатель ветвления компьютера будет угадывать, по существу, каждый раз). Рассмотрим:

Foo& Foo::operator=(const Foo& a)   // simpler, and probably much better
{
    s = a.s;
    i = a.i;
    return *this;
}

Ответ 4

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

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