# V690. The class implements a copy constructor/operator=, but lacks the operator=/copy constructor.

- Закон Большой Двойки
- Примечание

Анализатор обнаружил класс, в котором реализован конструктор копирования, но не реализован 'operator =', или наоборот, реализован 'operator =', но не реализован конструктор копирования.

Работать с такими классами очень опасно. Другими словами, нарушен "Закон Большой Двойки". Про этот закон, будет рассказано ниже.

Рассмотрим пример опасного класса. Пример длинный. Но сейчас важно только то, что в классе есть оператор присваивания, но нет конструктора копирования.

    class MyArray
    {
      char *m_buf;
      size_t m_size;
      void Clear() { delete [] m_buf; }
    public:
      MyArray() : m_buf(0), m_size(0) {}
      ~MyArray() { Clear(); }
      void Allocate(size_t s)
        { Clear(); m_buf = new char[s]; m_size = s; }
      void Copy(const MyArray &a)
        { Allocate(a.m_size);
          memcpy(m_buf, a.m_buf, a.m_size * sizeof(char)); }
      char &operator[](size_t i) { return m_buf[i]; }

      MyArray &operator =(const MyArray &a)
        { Copy(a); return *this; }
    };

Оставим в стороне практичность и полезность такого класса. Это всего лишь пример. Нам важно, что вот такой код, будет корректно работать:

    {
      MyArray A;
      A.Allocate(100);
      MyArray B;
      B = A;
    }

Массив успешно копируется с помощью оператора присваивания.

Следующий фрагмент кода приведёт к неопределенному поведению. Приложение упадёт или его работа будете нарушена как-то ещё.

    {
      MyArray A;
      A.Allocate(100);
      MyArray C(A);
    }

Дело в том, что в классе не реализован конструктор копирования. При создание объекта 'C', указатель на массив будет просто скопирован. Это приведёт к двоёному освобождению памяти при разрушении объектов A и C.

Аналогичная проблема будет, если реализован конструктор копирования, но нет оператора копирования.

Что-бы исправить класс, следует добавить конструктор копирования:

    MyArray &operator =(const MyArray &a)
      { Copy(a); return *this; }
    MyArray(const MyArray &a) : m_buf(0), m_size(0)
      { Copy(a); }

Если анализатор выдал предупреждение V690, то не ленитесь и реализуйте недостающий метод. Сделайте это, даже если код сейчас работает правильно, и вы помните, об особенностях класса. Пройдёт время, забудется отсутствие operator= или конструктора копирования. И вы или ваш коллега допустите ошибку, которую будет сложно найти. Когда поля класса скопированы автоматически, то часто, такой класс "почти работает". Неприятности проявляют себя позже в сосем другом месте программы.

## Закон Большой Двойки

Как было сказано в начале, диагностика V690 находит классы, в которых нарушен "Закон Большой Двойки". Рассмотрим это подробнее. Но начать надо с "Правило трёх". Обратимся к Wikipedia:

Правило трёх (также известное как "Закон Большой Тройки" или "Большая Тройка") — правило в C++, гласящее, что если класс или структура определяет один из следующих методов, то они должны явным образом определить все три метода:

- деструктор;
- конструктор копирования;
- оператор присваивания копированием.

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

Поправка к этому правилу заключается в том, что если используется RAII (от англ. Resource Acquisition Is Initialization), то используемый деструктор может остаться неопределённым (иногда упоминается как "Закон Большой Двойки").

Так как неявно определённые конструкторы и операторы присваивания просто копируют все члены-данные класса, определение явных конструкторов копирования и операторов присваивания копированием необходимо в случаях, когда класс инкапсулирует сложные структуры данных или может поддерживать эксклюзивный доступ к ресурсам. А также в случаях, когда класс содержит константные данные или ссылки.

Сам "Закон Большой Двойки" подробно рассматривается в этой статье: The Law of The Big Two.

Как видите, "Закон Большой Двойки" очень важен и поэтому мы реализовали соответствующую диагностику в анализаторе кода.

Начиная с C++11 появилась семантика перемещения, поэтому это правило расширилось до "Большой пятерки". Список методов, которые нужно определить все, если определён, хотя бы один из них:

- деструктор;
- конструктор копирования;
- оператор присваивания копированием;
- конструктор перемещения;
- оператор присваивания перемещением;

Поэтому всё, что справедливо для конструктора/оператора копирования, справедливо и для конструктора/оператора перемещения.

## Примечание

Всегда ли диагностика V690 говорит о наличии проблемы? Нет. Иногда никакой ошибки нет, но есть лишняя функция. Рассмотрим пример из реального приложения:

    struct wdiff {
      int start[2];
      int end[2];
      wdiff(int s1=0, int e1=0, int s2=0, int e2=0)
      {
        if (s1>e1) e1=s1-1;
        if (s2>e2) e2=s2-1;
        start[0] = s1;
        start[1] = s2;
        end[0] = e1;
        end[1] = e2;
      }
      wdiff(const wdiff & src)
      {
        for (int i=0; i<2; ++i)
        {
          start[i] = src.start[i];
          end[i] = src.end[i];
        }
      }
    };

В этом классе есть конструктор копирования, но нет оператора присваивания. Но это не страшно. Массивы 'start' и 'end' состоят из простых типов 'int'. Они будут корректно скопированы компилятором. Что-бы устранить предупреждение V690 нужно удалить бессмысленный конструктор копирования. Компилятор построит код, копирует элементы класса не медленней, а возможно даже быстрее.

Исправленный вариант:

    struct wdiff {
      int start[2];
      int end[2];
      wdiff(int s1=0, int e1=0, int s2=0, int e2=0)
      {
        if (s1>e1) e1=s1-1;
        if (s2>e2) e2=s2-1;
        start[0] = s1;
        start[1] = s2;
        end[0] = e1;
        end[1] = e2;
      }
    };

Взгляните на примеры ошибок, обнаруженных с помощью диагностики V690.