Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
to the top

Вебинар: Использование статических анализаторов кода при разработке безопасного ПО - 19.12

>
>
>
V690. The class implements a copy const…
menu mobile close menu
Проверка проектов
Сообщения PVS-Studio
Диагностики общего назначения (General Analysis, C++)
Диагностики общего назначения (General Analysis, C#)
Диагностики общего назначения (General Analysis, Java)
Микрооптимизации (C++)
Диагностика 64-битных ошибок (Viva64, C++)
Реализовано по запросам пользователей (C++)
Cтандарт MISRA
Стандарт AUTOSAR
Стандарт OWASP (C++)
Стандарт OWASP (C#)
Проблемы при работе анализатора кода
Дополнительная информация
toggle menu Оглавление

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

16 Май 2014

Анализатор обнаружил класс, в котором реализован конструктор копирования, но не реализован '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.

close form

Заполните форму в два простых шага ниже:

Ваши контактные данные:

Шаг 1
Поздравляем! У вас есть промокод!

Тип желаемой лицензии:

Шаг 2
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности
close form
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Бесплатная лицензия PVS‑Studio для специалистов Microsoft MVP
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
check circle
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте, отфильтровано ли письмо в одну из следующих стандартных папок:

  • Промоакции
  • Оповещения
  • Спам