Явно используемые по умолчанию и удаленные функции

В C++11 установленные по умолчанию и удаленные функции позволяют явным образом указывать, будут ли специальные функции-члены создаваться автоматически. Удаленные функции также предоставляют простой язык, чтобы предотвратить повышение проблемного типа в аргументах для функций всех типов — специальных функций-членов, а также обычных функций-членов и немемблерных функций, что в противном случае приведет к нежелательному вызову функции.

Преимущества явным образом установленных по умолчанию и удаленных функций

В C++компилятор автоматически создает конструктор по умолчанию, конструктор копирования, оператор назначения копирования и деструктор для типа, если он не объявляет свой собственный. Эти функции называются специальными функциями-членами, и это то, что делает простые определяемые пользователем типы в C++ вести себя как структуры в C. То есть можно создавать, копировать и уничтожать их без дополнительных усилий по программированию. В C++11 в язык привносится семантика перемещения: для этого в список специальных функций-членов, которые компилятор может создавать автоматически, добавлены конструктор перемещения и оператор перемещения и присваивания.

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

  • Если конструктор был объявлен явным образом, то автоматическое создание конструктора не выполняется.

  • Если виртуальный деструктор был объявлен явным образом, то автоматическое создание деструктора не выполняется.

  • Если конструктор перемещения или оператор перемещения и присваивания был объявлен явным образом, то:

    • автоматическое создание конструктора копии не выполняется;

    • автоматическое создание оператора копирования и присваивания не выполняется.

  • Если конструктор копии, оператор копирования и присваивания, конструктор перемещения, оператор перемещения и присваивания или деструктор был объявлен явным образом, то:

    • автоматическое создание конструктора перемещения не выполняется;

    • автоматическое создание оператора перемещения и присваивания не выполняется.

Примечание.

Кроме того, в стандарте C++11 определены следующие дополнительные правила.

  • Если конструктор копии или деструктор был объявлен явным образом, то автоматическое создание оператора копирования и присваивания не рекомендуется.
  • Если оператор копирования и присваивания или деструктор был объявлен явным образом, то автоматическое создание конструктора копии не рекомендуется.

В обоих случаях Visual Studio продолжает автоматически создавать необходимые функции неявно и не выдает предупреждения по умолчанию. Так как Visual Studio 2022 версии 17.7, C5267 можно включить для выдачи предупреждения.

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

Эти правила могут усложнять реализацию того, что должно быть прямым, определяемым пользователем типами и общими идиомами C++, например, делая определяемый пользователем тип неcopyable, объявляя конструктор копирования и оператор назначения копирования частным образом и не определяя их.

struct noncopyable
{
  noncopyable() {};

private:
  noncopyable(const noncopyable&);
  noncopyable& operator=(const noncopyable&);
};

До C++11 этот фрагмент кода был идиоматической формой нескопируемых типов. Однако здесь возникает ряд проблем.

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

  • Даже если явно определенный конструктор по умолчанию ничего не делает, компилятор считает его нетривиальным. Это не так эффективно, как автоматическое создание конструктора по умолчанию, поскольку в этом случае тип noncopyable не может являться истинным типом POD.

  • Хотя конструктор копии и оператор копирования и присваивания скрыты от внешнего кода, однако функции-члены и дружественные функции для типа noncopyable все равно могут их видеть и вызывать. Если они объявлены, но не определены, вызов их вызывает ошибку компоновщика.

  • Хотя это обычно принято идиом, намерение не ясно, если вы не понимаете все правила автоматического создания специальных функций-членов.

В C++11 неcopyable idiom можно реализовать таким образом, что проще.

struct noncopyable
{
  noncopyable() =default;
  noncopyable(const noncopyable&) =delete;
  noncopyable& operator=(const noncopyable&) =delete;
};

Обратите внимание, как разрешаются проблемы с такими идиомами в версиях до C++11.

  • Создание конструктора по умолчанию по-прежнему можно предотвратить путем объявления конструктора копии, однако его можно восстановить, явным установив его по умолчанию.

  • Явным образом специальные функции-члены по умолчанию по-прежнему считаются тривиальными, поэтому нет штрафа за производительность и noncopyable не запрещается быть истинным типом POD.

  • Конструктор копии и оператор копирования и присваивания являются открытыми, но удаленным. Это ошибка во время компиляции для определения или вызова удаленной функции.

  • Намерение ясно всем, кто разобрался в сути ключевых слов =default и =delete. Понимать правила автоматического создания специальных функций-членов не требуется.

Аналогичные идиомы существуют для создания определяемых пользователем типов, которые являются недвижимыми, которые могут быть динамически выделены или не могут быть динамически выделены. Для каждой из этих идиом имеются реализации в версиях до C++11, которым свойственны аналогичные проблемы и которые в C ++11 разрешаются схожим образом — путем реализации их в виде установленных по умолчанию и удаленных специальных функций-членов.

Явно установленные по умолчанию функции

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

Специальная функция-член устанавливается по умолчанию путем ее объявления, как показано в следующем примере.

struct widget
{
  widget()=default;

  inline widget& operator=(const widget&);
};

inline widget& widget::operator=(const widget&) =default;

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

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

Удаленные функции

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

struct widget
{
  // deleted operator new prevents widget from being dynamically allocated.
  void* operator new(std::size_t) = delete;
};

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

// deleted overload prevents call through type promotion of float to double from succeeding.
void call_with_true_double_only(float) =delete;
void call_with_true_double_only(double param) { return; }

Обратите внимание, что в предыдущем примере вызов call_with_true_double_only с помощью float аргумента приведет к ошибке компилятора, но вызов call_with_true_double_only с помощью int аргумента не будет. В int этом случае аргумент будет повышен от intdouble и успешно вызывает double версию функции, даже если это не может быть то, что вы планируете. Чтобы убедиться, что любой вызов этой функции с помощью не двойного аргумента вызывает ошибку компилятора, можно объявить версию шаблона удаленной функции.

template < typename T >
void call_with_true_double_only(T) =delete; //prevent call through type promotion of any T to double from succeeding.

void call_with_true_double_only(double param) { return; } // also define for const double, double&, etc. as needed.