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

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

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

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

```cpp
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; }
};
```

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

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

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

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

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

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

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

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

```cpp
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](https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B0%D0%B2%D0%B8%D0%BB%D0%BE_%D1%82%D1%80%D1%91%D1%85_%28C%2B%2B_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%29):

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

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

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

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

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

Сам "Закон Большой Двойки" подробно рассматривается в этой статье: [The Law of The Big Two](https://www.artima.com/cppsource/bigtwo.html)\.

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

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

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

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

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

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

```cpp
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 нужно удалить бессмысленный конструктор копирования\. Компилятор построит код, копирует элементы класса не медленней, а возможно даже быстрее\.

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

```cpp
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;
  }
};
```