Классы C++ имеют следующие специальные функции-члены:

  • Конструктор
  • Деструктор
  • Копировать конструктор
  • Скопировать задание

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

Неявное определение

Предположим, у нас есть класс. Person с двумя атрибутами, строкой name и целым числом age . Ниже приведен пример специальных функций-членов, сгенерированных компилятором для Person:

В настоящее время наш класс не владеет памятью, на которую указывает name. Если память, на которую указывает byname, освобождается из-за пределов класса, Person в конечном итоге будет иметь висячий указатель. Естественно, наш класс захочет получить полную собственность на память, используемую name. Следовательно, мы можем определить параметризованный конструктор, как показано ниже:

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

Правило трех гласит, что если нам нужно явно определить деструктор, конструктор копирования или оператор присваивания копии, мы должны определить все три функции. Явное определение специальных функций-членов обычно необходимо, когда класс управляет ресурс. В таких случаях обычно рекомендуется определить все три, чтобы правильно управлять ресурсом.

В нашем конкретном примере класса Person вызов неявно определенного конструктора копирования или оператора присваивания копии снова приведет к тому, что name будет указывать на память, не принадлежащую этому классу, что может привести к оборванному указателю и сбою. Кроме того, вызов оператора присваивания копии также приводит к утечке памяти.

Явное определение специальных функций-членов

Чтобы исправить вышеуказанные проблемы, мы можем явно определить функции, как показано ниже:

Условие if(this != that) в операции присваивания копии — это просто оптимизация, позволяющая избежать ненужного копирования, выделения и освобождения памяти. Однако такая ситуация редко встречается на практике, поэтому также можно исключить эту дополнительную проверку каждый раз, когда нам нужно скопировать объект в другой объект.

Это хорошо, но все еще остается проблема с определением оператора присваивания копии. Оператор new в C++ потенциально может генерировать исключение bad_alloc, если ему не удается выделить память. Если оператор new бросает вызов, мы оставляем наш класс в неопределенном состоянии, поскольку память, на которую указывает name, была удалена, но не содержит нового name. Чтобы справиться с этим, мы можем немного изменить наш конструктор копирования, чтобы создать локальную копию новых данных перед удалением старых. Если при создании локальной копии произойдет сбой выделения памяти, наши старые данные останутся нетронутыми.

Теперь это выглядит великолепно. Однако этот класс управляет только одним ресурсом. Размер функции может быстро увеличиваться при увеличении количества ресурсов. В следующем уроке мы увидим идиому копирования и обмена, чтобы реализовать то же поведение более чистым способом.

Посты в этой серии

  1. Правило трех
  2. Копировать и заменять идиомы
  3. Правило пяти
  4. Ключевые слова по умолчанию и удалить
  5. Особые правила генерации участников