#ИМЯ?

Я знаю, что вместо написания:

class A {
public:
    A(A&&) noexcept = default;
};

Лучше писать

class A {
public:
    A(A&&) noexcept;
};

inline A::A(A&&) noexcept = default;

Причины, о которых я слышал:

  1. Он избегает конструктора, который deleted. Компилятор выдаст ошибку, если он не может определить функцию.

  2. Конструктор перемещения объявляется noexcept даже если некоторые из конструкторов перемещения полей элементов не аннотируются с noexcept.

Может ли кто-нибудь объяснить немного больше об теории различий?

Ответ 1

Для описания класса/метода используется только объявление, поэтому при выполнении

class A {
public:
    A(A&&) noexcept;
};

Вы даже можете реализовать A::A(A&&) (определение может быть в разных TU)

Когда вы реализуете его с помощью:

A::A(A&&) noexcept = default;

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

Но когда вы объявляете его внутри класса:

class A {
public:
    A(A&&) noexcept = default;
};

Это "часть" декларации. поэтому он может быть неявно удален (из-за члена или базового класса).

То же самое относится к noexcept.

Другим преимуществом для определения определения в выделенном TU является то, что определение требуемых зависимостей может быть только в этом TU, а не в каждом месте, где будет создан метод. (Полезно для идиомы pimpl, например).

Один из недостатков определения разделения и объявления заключается в том, что теперь метод "предоставляется пользователем", который может влиять на характеристики как тривиально-неразрушаемые/скопируемые/...

Ответ 2

Поведение описано в [dcl.fct.def.default] p3, в котором говорится:

Если функция, которая явно дефолтна, объявляется с помощью спецификатора noexcept, который не дает той же спецификации исключения, что и неявное объявление (18.4), тогда

(3.1) - если функция явно дефолтована по первому объявлению, она определяется как удаленная;

(3.2) - в противном случае программа плохо сформирована.

Обратите внимание на изменение формулировок на С++ 20, но намерение для этого случая одинаково. Я считаю, что формулировка С++ 17 проще для поиска.

Например:

struct S {
      S( S&& ) noexcept(false) = default;
};

Конструктор перемещения определяется как удаленный, поскольку из-за [except.spec] p7:

Конструктор с неявным объявлением для класса X или конструктор без спецификатора noexcept, который имеет дефолт по его первому объявлению, имеет потенциально-бросающую спецификацию исключений тогда и только тогда, когда любая из следующих конструкций потенциально бросает:

(7.1) - конструктор, выбранный с помощью разрешения перегрузки в неявном определении конструктора для класса X, для инициализации потенциально сконструированного подобъекта или

(7.2) - подвыражение такой инициализации, такое как выражение аргумента по умолчанию, или,

(7.3) - для конструктора по умолчанию - инициализатор элемента по умолчанию.

ни один из случаев не выполняется.

Если мы вернемся к [dcl.fct.def.default] p3, в противном случае программа будет плохо сформирована. Искаженные программы требуют диагностики, поэтому, если мы изменим первый пример следующим образом (см. Его в прямом эфире):

struct S {
   S( S&& ) noexcept(false) ;
private:
  int i;

};

S::S( S&&) noexcept(false) = default ;

он будет производить диагностику, например:

error: function 'S::S(S&&)' defaulted on its redeclaration with an exception-specification that differs from the implicit exception-specification 'noexcept'
 S::S( S&&) noexcept(false) = default ;
 ^

Обратите внимание на clang-ошибку, связанную с этим случаем, кажется, что они не следуют из отчета о дефектах 1778.

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