15 Special member functions [special]

15.8 Copying and moving class objects [class.copy]

Объект класса можно скопировать или переместить двумя способами: путем инициализации ([class.ctor],[dcl.init]), в том числе для передачи аргументов функции ([expr.call]) и дляfunction value return; и поassignment. Концептуально эти две операции реализуются конструктором копирования / перемещения ([class.ctor]) и оператором присваивания копирования / перемещения ([over.ass]).

Программа неправильно сформирована, если конструктор копирования / перемещения или оператор присваивания копирования / перемещения для объекта неявно используется odr, а специальная функция-член - нетaccessible. [ Note: Копирование / перемещение одного объекта в другой с помощью конструктора копирования / перемещения или оператора присваивания копирования / перемещения не изменяет макет или размер любого объекта. ]end note

15.8.1 Copy/move constructors [class.copy.ctor]

Конструктор без шаблона для класса X является ,copy constructor если ее первый параметр имеет тип X&, const X&, volatile X& или const volatile X&, и либо нет других параметров или также все другие параметрыdefault arguments. [ Example: X​::​X(const X&) и X​::​X(X&,int=1) являются конструкторами копирования.

struct X {
  X(int);
  X(const X&, int = 1);
};
X a(1);             // calls X(int);
X b(a, 0);          // calls X(const X&, int);
X c = b;            // calls X(const X&, int);

end example]

Конструктор без шаблона для классаX является ,move constructor если ее первый параметр имеет типX&&,const X&&, volatile X&&илиconst volatile X&&, и либо нет других параметров или также все другие параметрыdefault arguments. [ Example:Y​::​Y(Y&&) - конструктор перемещения.

struct Y {
  Y(const Y&);
  Y(Y&&);
};
extern Y f(int);
Y d(f(1));          // calls Y(Y&&)
Y e = d;            // calls Y(const Y&)

end example]

[ Note: Все формы конструктора копирования / перемещения могут быть объявлены для класса. [Example:

struct X {
  X(const X&);
  X(X&);            // OK
  X(X&&);
  X(const X&&);     // OK, but possibly not sensible
};

end example] ]end note

[ Note: Если класс X имеет только конструктор копирования с параметром типа X&, инициализатор типа const X или volatile X не может инициализировать объект типа (возможно, с квалификацией cv) X. [Example:

struct X {
  X();              // default constructor
  X(X&);            // copy constructor with a non-const parameter
};
const X cx;
X x = cx;           // error: X​::​X(X&) cannot copy cx into x

end example] ]end note

Объявление конструктора для класса X неправильно сформировано, если его первый параметр имеет тип (необязательно cv-квалифицированный) X и либо нет других параметров, либо все другие параметры имеют аргументы по умолчанию. Шаблон функции-члена никогда не создается для создания такой сигнатуры конструктора. [Example:

struct S {
  template<typename T> S(T);
  S();
};

S g;

void h() {
  S a(g);           // does not instantiate the member template to produce S​::​S<S>(S);
                    // uses the implicitly declared copy constructor
}

end example]

Если в определении класса явно не объявляется конструктор копирования, объявляется неявный конструкторimplicitly. Если в определении класса объявляется конструктор перемещения или оператор присваивания перемещения, неявно объявленный конструктор копирования определяется как удаленный; в противном случае он определяется как defaulted. Последний случай считается устаревшим, если в классе есть объявленный пользователем оператор присваивания копии или объявленный пользователем деструктор.

Неявно объявленный конструктор копирования для класса X будет иметь форму

X::X(const X&)

если каждый потенциально сконструированный подобъект типа класса M (или его массива) имеет конструктор копирования, первый параметр которого имеет тип const M& или const volatile M&.119 В противном случае неявно объявленный конструктор копии будет иметь вид

X::X(X&)

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

  • X не имеет объявленного пользователем конструктора копирования,

  • X не имеет объявленного пользователем оператора присваивания копии,

  • X не имеет объявленного пользователем оператора присваивания перемещения, и

  • X не имеет деструктора, объявленного пользователем.

[ Note: Когда конструктор перемещения не объявляется неявно или не предоставляется явно, выражения, которые в противном случае вызвали бы конструктор перемещения, могут вместо этого вызывать конструктор копирования. ]end note

Неявно объявленный конструктор перемещения для классаX будет иметь вид

X::X(X&&)

Неявно объявленный конструктор копирования / перемещения является inline public членом своего класса. Конструктор копирования / перемещения по умолчанию для класса X определяется так, какdeleted если бы онX имел:

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

  • потенциально сконструированный тип подобъекта M (или его массив), который не может быть скопирован / перемещен, поскольку overload resolutionв применении кMсоответствующему конструктору find приводит к неоднозначности или к функции, которая удаляется или недоступна из конструктора по умолчанию,

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

  • для конструктора копирования - нестатический член данных ссылочного типа rvalue.

Конструктор перемещения по умолчанию, который определен как удаленный, игнорируется разрешением перегрузки ([over.match],[over.over]). [ Note: В противном случае удаленный конструктор перемещения помешал бы инициализации из rvalue, который вместо этого может использовать конструктор копирования. ]end note

Конструктор копирования / перемещения для класса X является тривиальным, если он не предоставляется пользователем и если:

  • класс X не имеетvirtual functions и нетvirtual base classes, и

  • конструктор, выбранный для копирования / перемещения каждого прямого подобъекта базового класса, является тривиальным, и

  • для каждого нестатического члена данных, X который относится к типу класса (или его массиву), конструктор, выбранный для копирования / перемещения этого члена, является тривиальным;

в противном случае - конструктор копирования / перемещения non-trivial.

Конструктор копирования / перемещения, который установлен по умолчанию и не определен как удаленный, - implicitly defined это если он естьodr-used или когда он явно задан по умолчанию после его первого объявления. [ Note: Конструктор копирования / перемещения определяется неявно, даже если реализация опускает егоodr-use ([class.temporary]). ] Если неявно определенный конструктор удовлетворяет требованиям a , то неявно определенный конструктор будет .end noteconstexpr constructorconstexpr

Прежде чем конструктор копирования / перемещения по умолчанию для класса будет неявно определен, все не предоставленные пользователем конструкторы копирования / перемещения для его потенциально созданных подобъектов должны быть неявно определены. [ Note: Неявно объявленный конструктор копирования / перемещения имеет подразумеваемыйexception specification. ]end note

Неявно определенный конструктор копирования / перемещения для класса, неX являющегося объединением, выполняет поэлементное копирование / перемещение его баз и членов. [ Note: Инициализаторы нестатических элементов данных по умолчанию игнорируются. См. Также пример в[class.base.init]. ] Порядок инициализации такой же, как порядок инициализации баз и членов в определяемом пользователем конструкторе (см. ). Пусть будет либо параметром конструктора, либо, для конструктора перемещения, значением xvalue, относящимся к параметру. Каждый базовый или нестатический член данных копируется / перемещается в соответствии с его типом:end note[class.base.init]x

  • если член является массивом, каждый элемент напрямую инициализируется соответствующим подобъектомx;

  • если членm имеет ссылочный тип rvalueT&&, он инициализируется напрямую с помощью static_­cast<T&&>(x.m);

  • в противном случае база или член инициализируются напрямую соответствующей базой или членомx.

Подобъекты виртуального базового класса должны быть инициализированы только один раз неявно определенным конструктором копирования / перемещения (см[class.base.init]. Раздел "Ресурсы" ).

Неявно определенный конструктор копирования / перемещения для союзной X копируетobject representation изX.

Это означает, что ссылочный параметр неявно объявленного конструктора копирования не может связываться с volatile lvalue; см[diff.special].

15.8.2 Copy/move assignment operator [class.copy.assign]

Пользователь объявленнойcopy оператор присваиванияX​::​operator= не является членом статической функцией не-шаблона классаX ровно с одним параметром типаX,X&,constX&, volatileX& илиconstvolatile X&.120 [ Note: Должен быть объявлен перегруженный оператор присваивания, имеющий только один параметр; см[over.ass]. ] [ Для класса может быть объявлено более одной формы оператора присваивания копии. ] [ Если у класса есть только оператор присваивания копии с параметром типа , выражение типа const не может быть присвоено объекту типа . [end noteNote: end noteNote: XX&XXExample:

struct X {
  X();
  X& operator=(X&);
};
const X cx;
X x;
void f() {
  x = cx;           // error: X​::​operator=(X&) cannot assign cx into x
}

end example] ]end note

Если в определении класса явно не объявляется оператор присваивания копии, он объявляетсяimplicitly. Если определение класса объявляет конструктор перемещения или оператор присваивания перемещения, неявно объявленный оператор присваивания копии определяется как удаленный; в противном случае он определяется как defaulted. Последний случай считается устаревшим, если в классе есть объявленный пользователем конструктор копии или объявленный пользователем деструктор. Неявно объявленный оператор присваивания копии для класса X будет иметь вид

X& X::operator=(const X&)

если

  • каждый непосредственный базовый класс B из X имеет оператор присваивания , копирование параметры которого имеет типа const B&, const volatile B& или B, и

  • для всех нестатических членов данных, X которые относятся к типу класса M (или его массиву), каждый такой тип класса имеет оператор присваивания копии, параметр которого имеет тип const M&, const volatile M& или M.121

В противном случае неявно объявленный оператор присваивания копии будет иметь вид

X& X::operator=(X&)

Пользователь объявленнойmove оператор присваиванияX​::​operator= не является членом статической функцией не-шаблона классаX ровно с одним параметром типаX&&,const X&&,volatile X&&, или const volatile X&&. [ Note: Должен быть объявлен перегруженный оператор присваивания, имеющий только один параметр; см[over.ass]. ] [ Для класса может быть объявлено более одной формы оператора присваивания перемещения. ]end noteNote: end note

Если в определении классаX явно не объявляется оператор присваивания перемещения, он будет неявно объявлен как принятый по умолчанию тогда и только тогда, когда

  • X не имеет объявленного пользователем конструктора копирования,

  • X не имеет конструктора перемещения, объявленного пользователем,

  • X не имеет объявленного пользователем оператора присваивания копии, и

  • X не имеет деструктора, объявленного пользователем.

[ Example: Определение класса

struct S {
  int a;
  S& operator=(const S&) = default;
};

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

struct S {
  int a;
  S& operator=(const S&) = default;
  S& operator=(S&&) = default;
};

end example]

Неявно объявленный оператор присваивания перемещения для классаX будет иметь вид

X& X::operator=(X&&);

Неявно объявленный оператор присваивания копирования / перемещения для класса X имеет тип возвращаемого значения X&; он возвращает объект, для которого вызывается оператор присваивания, то есть объект, которому назначен. Неявно объявленный оператор присваивания копирования / перемещения является inline public членом своего класса.

Оператор присваивания копирования / перемещения по умолчанию для классаX определяется как удаленный, еслиX он:

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

  • нестатический член данныхconst неклассового типа (или его массив), или

  • нестатический член данных ссылочного типа, или

  • прямой нестатический член данных типа классаM (или его массива) или прямой базовый класс,M который не может быть скопирован / перемещен, посколькуoverload resolutionв применении кMсоответствующему оператору присваивания find приводит к неоднозначности или к функции, которая удаляется или недоступна из оператора присваивания по умолчанию.

Оператор присваивания перемещения по умолчанию, который определен как удаленный, игнорируется разрешением перегрузки ([over.match],[over.over]).

Поскольку оператор присваивания копирования / перемещения неявно объявляется для класса, если он не объявлен пользователем, оператор присваивания копирования / перемещения базового класса всегда скрывается соответствующим оператором присваивания производного класса ([over.ass]). A, using-declarationкоторый переносит из базового класса оператор присваивания с типом параметра, который может быть типом оператора присваивания копирования / перемещения для производного класса, не считается явным объявлением такого оператора и не подавляет неявное объявление производного оператор класса; оператор, представленный с помощью using-declaration , скрыт неявно объявленным оператором в производном классе.

Оператор присваивания копирования / перемещения для класса X является тривиальным, если он не предоставляется пользователем и если:

  • класс X не имеетvirtual functions и нетvirtual base classes, и

  • оператор присваивания, выбранный для копирования / перемещения каждого прямого подобъекта базового класса, является тривиальным, и

  • для каждого нестатического элемента данных X , принадлежащего к типу класса (или его массиву), оператор присваивания, выбранный для копирования / перемещения этого члена, является тривиальным;

в противном случае используется оператор присваивания копирования / перемещения non-trivial.

Оператор присваивания копирования / перемещения для класса,X который установлен по умолчанию и не определен как удаленный, - implicitly defined это когда он естьodr-used (например, когда он выбран разрешением перегрузки для назначения объекту своего типа класса) или когда он явно используется по умолчанию после его первого декларация. Неявно определенный оператор присваивания копирования / перемещения - этоconstexpr если

  • X это буквальный тип, а

  • оператор присваивания, выбранный для копирования / перемещения каждого прямого подобъекта базового класса, является функцией constexpr, и

  • для каждого нестатического члена данных,X имеющего тип класса (или его массив), оператор присваивания, выбранный для копирования / перемещения этого члена, является функцией constexpr.

Перед тем как оператор присваивания копирования / перемещения по умолчанию для класса будет неявно определен, все операторы присваивания копирования / перемещения, не предоставляемые пользователем, для его прямых базовых классов и его нестатических элементов данных должны быть неявно определены. [ Note: Неявно объявленный оператор присваивания копирования / перемещения имеет подразумеваемыйexception specification. ]end note

Неявно определенный оператор присваивания копирования / перемещения для класса, не являющегося объединением,X выполняет поэлементное присвоение его подобъектов копированию / перемещению. ПервымиX назначаются прямые базовые классы в порядке их объявления в base-specifier-list, а затемX назначаются непосредственные нестатические члены данных в том порядке, в котором они были объявлены в определении класса. Пустьx будет либо параметром функции, либо, для оператора перемещения, значением x, относящимся к параметру. Каждому подобъекту присваивается способ, соответствующий его типу:

  • если подобъект имеет тип класса, как если бы при вызовеoperator= с подобъектом в качестве выражения объекта и соответствующим подобъектомx в качестве единственного аргумента функции (как если бы посредством явной квалификации; то есть игнорирование любых возможных виртуальных замещающих функций в более производных классы);

  • если подобъект является массивом, каждому элементу присваивается способ, соответствующий типу элемента;

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

Не указано, назначаются ли подобъекты, представляющие виртуальные базовые классы, более одного раза неявно определенным оператором присваивания копирования / перемещения. [Example:

struct V { };
struct A : virtual V { };
struct B : virtual V { };
struct C : B, A { };

Не указано,V назначается ли подобъект виртуального базового класса дважды неявно определенным оператором присваивания копирования / перемещения для C. ]end example

Неявно определенный оператор копирующего присваивания для союзнойX копируетobject representation изX.

Поскольку оператор присваивания шаблона или оператор присваивания, принимающий параметр ссылки rvalue, никогда не является оператором присваивания копии, наличие такого оператора присваивания не подавляет неявное объявление оператора присваивания копии. Такие операторы присваивания участвуют в разрешении перегрузки с другими операторами присваивания, включая операторы присваивания копий, и, если они выбраны, будут использоваться для назначения объекта.

Это означает, что ссылочный параметр неявно объявленного оператора присваивания копии не может связываться с volatile lvalue; см[diff.special].

15.8.3 Copy/move elision [class.copy.elision]

При соблюдении определенных критериев реализации разрешается опускать конструкцию копирования / перемещения объекта класса, даже если конструктор, выбранный для операции копирования / перемещения, и / или деструктор для объекта имеют побочные эффекты. В таких случаях реализация рассматривает источник и цель пропущенной операции копирования / перемещения как просто два разных способа ссылки на один и тот же объект. Если первым параметром выбранного конструктора является ссылка rvalue на тип объекта, уничтожение этого объекта происходит тогда, когда цель была бы уничтожена; в противном случае разрушение происходит в более позднее время, когда два объекта были бы уничтожены без оптимизации.122 Это вызванное исключение операций копирования / перемещения разрешено в следующих случаях (которые могут быть объединены для исключения нескольких копий):copy elision

  • вreturn операторе функции с типом возвращаемого класса, когда expressionэто имя энергонезависимого автоматического объекта (кроме параметра функции или переменной, введенной с помощью exception-declarationa handler([except.handle])) с тем же типом (игнорируя cv-квалификацию ) в качестве возвращаемого типа функции, операцию копирования / перемещения можно опустить, сконструировав автоматический объект непосредственно в возвращаемом объекте вызова функции.

  • в a throw-expression, когда операнд является именем энергонезависимого автоматического объекта (кроме функции или параметра catch-clause), область действия которого не выходит за пределы самого внутреннего охватывающего объекта try-block(если он есть), копирование / перемещение Операция от операнда доexception object может быть опущена путем создания автоматического объекта непосредственно в объекте исключения

  • когда exception-declarationобработчик исключения (Clause[except]) объявляет объект того же типа (за исключением cv-qualification), что и объект exception object, операция копирования может быть опущена, рассматривая exception-declarationкак псевдоним для объекта исключения, если значение программы будет без изменений, за исключением выполнения конструкторов и деструкторов для объекта, объявленного классом exception-declaration. [ Note: Не может быть перехода от объекта исключения, потому что это всегда lvalue. ] end note

Копирование требуется, когда выражение оценивается в контексте, требующем aconstant expression и inconstant initialization. [ Note: Копирование может не выполняться, если то же выражение оценивается в другом контексте. ]end note

[Example:

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};

Thing f() {
  Thing t;
  return t;
}

Thing t2 = f();

struct A {
  void *p;
  constexpr A(): p(this) {}
};

constexpr A g() {
  A a;
  return a;
}

constexpr A a;          // well-formed, a.p points to a
constexpr A b = g();    // well-formed, b.p points to b

void g() {
  A c = g();            // well-formed, c.p may point to c or to an ephemeral temporary
}

Здесь критерии исключения могут исключить копирование локального автоматического объекта t в объект результата для вызова функции f(), который является глобальным объектом t2. Фактически создание локального объекта t можно рассматривать как прямую инициализацию глобального объекта t2, и уничтожение этого объекта произойдет при выходе из программы. Добавление конструктора перемещения вThing имеет тот же эффект, ноt2 исключается именно конструкция перемещения от локального автоматического объекта к нему. ]end example

В следующих контекстах инициализации копирования вместо операции копирования может использоваться операция перемещения:

  • Если expressionin areturn statement - это (возможно id-expression , заключенное в скобки), которое именует объект с автоматической продолжительностью хранения, объявленной в теле или parameter-declaration-clauseсамой внутренней включающей функции, или lambda-expression, или

  • если операнд a throw-expression является именем энергонезависимого автоматического объекта (кроме функции или параметра catch-clause), область действия которого не выходит за пределы самого внутреннего охватывающего объекта try-block(если он есть),

разрешение перегрузки для выбора конструктора для копии сначала выполняется, как если бы объект был обозначен rvalue. Если первое разрешение перегрузки завершилось неудачно или не было выполнено, или если тип первого параметра выбранного конструктора не является ссылкой rvalue на тип объекта (возможно, с квалификацией cv), разрешение перегрузки выполняется снова, рассматривая объект как lvalue. [ Note: Это двухэтапное разрешение перегрузки должно выполняться независимо от того, произойдет ли исключение копирования. Он определяет конструктор, который будет вызываться, если исключение не выполнено, и выбранный конструктор должен быть доступен, даже если вызов отклонен. ]end note

[Example:

class Thing {
public:
  Thing();
  ~Thing();
  Thing(Thing&&);
private:
  Thing(const Thing&);
};

Thing f(bool b) {
  Thing t;
  if (b)
    throw t;            // OK: Thing(Thing&&) used (or elided) to throw t
  return t;             // OK: Thing(Thing&&) used (or elided) to return t
}

Thing t2 = f(false);    // OK: no extra copy/move performed, t2 constructed by call to f

struct Weird {
  Weird();
  Weird(Weird&);
};

Weird g() {
  Weird w;
  return w;             // OK: first overload resolution fails, second overload resolution selects Weird(Weird&)
}

end example]

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