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

Я играл с конструкторами по умолчанию и заметил странное поведение (с моей точки зрения).

Когда я объявляю A() = default, я не получаю ошибки компоновщика.

struct A
{
  int a;
  A() = default;
};

A a; // no linker error

Однако, когда я объявляю A();, я его получаю.

struct A
{
  int a;
  A();
};

A a; // linker error - undefined reference to `A::A()`

Вопросов:

  1. В чем разница между ними?

  2. И если A(); выдает ошибку компоновщика, почему он вообще поддерживается? Есть практические приложения?

ОБНОВЛЕНИЕ (дополнительный вопрос)

Для A();, почему он не может быть неявно определен компилятором, если определение не указано пользователем?

A(); только объявляет, что конструктор существует, но не определяет его. Поскольку компоновщик не может найти определение, вы получите ошибку undefined reference.
super 02.04.2018 09:11

@super и в первом случае =default определяет ctor.

Jean-Baptiste Yunès 02.04.2018 09:35

@code - есть третий вариант, при котором вы вообще не упоминаете A() в объявлении. Затем компилятор попытается создать эквивалент A() = default;, если такой конструктор необходим. Это неявный вариант.

Bo Persson 02.04.2018 09:46

@BoPersson, правда. вопрос в том, может ли компилятор неявно определять конструктор, когда класс не определил его явно, почему компилятор не может предположить, что A(); будет A() = default, если пользователь не определил его явно? @Vlad уже ответил. Мне любопытны твои мысли.

Joseph D. 02.04.2018 09:52

@code - как говорит Влад, согласно языковым правилам A(); "определяется пользователем", поэтому пользователь должен его определить. Причина в том, что так было решено, так «потому что».

Bo Persson 02.04.2018 10:00

спасибо @BoPersson! ценим ваш отзыв.

Joseph D. 02.04.2018 10:08
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
6
737
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

В первом случае конструктор определяет сам компилятор. Учтите, что в первом случае объявление конструктора совпадает с его определением.

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

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

Например

#include <iostream>

struct A
{
    int a;
    A();
};

A::A() = default;

int main() 
{
    A a;

    return 0;
}

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

Joseph D. 02.04.2018 09:26

@codekaizer Если конструктор вызывается, он должен быть определен.

Vlad from Moscow 02.04.2018 09:28

правда. простите мое любопытство, но почему компилятор просто не откатится к A() = default, если пользователь не определил A();?

Joseph D. 02.04.2018 09:30

@codekaizer Потому что программа может состоять из нескольких юнитов компиляции. Таким образом, компилятор не может знать, является ли это ошибкой пользователя.

Vlad from Moscow 02.04.2018 09:32
Ответ принят как подходящий

Цель написания

A();

состоит в том, чтобы объявить компилятору, что определение того, что на самом деле должна делать A (), будет дано где-то еще (вами!), и если оно дано в другом модуле компиляции (cpp-файле), компоновщик отвечает за поиск этого определения .

A() = default;

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

Почему поддерживается объявление без определения A () в первую очередь? Потому что вы хотите иметь возможность независимо компилировать разные cpp-файлы. В противном случае вам всегда придется компилировать весь свой код, даже если 99% его не изменилось.

Построение A, скорее всего, будет определено в "A.cpp". Если вы завершили разработку своей структуры A, то в идеале "A.cpp" будет компилироваться один раз и больше никогда. Если вы создаете A в другом классе / структуре / модуле компиляции «B.cpp», то компилятор может доверять существованию определения для A () при компиляции «B.cpp», не зная, как это определение на самом деле выглядит. .

Что касается следующего вопроса, «почему это не может быть определено неявно»: вероятно, это неправильное понимание того, почему возникают ошибки. Ошибки компилятора / компоновщика не возникают, чтобы вас наказать. Они не означают, что компилятор делает вид, что не может что-то делать, хотя может. Ошибки возникают, чтобы напомнить вам, что вы нарушаете свои собственные обещания, и любая попытка компилятора или компоновщика исправить это может быть возможна, но, скорее всего, не будет работать так, как вы хотите, потому что есть признаки того, что вы потеряли из виду свои собственные требования.

При этом A () можно определить неявно. Но если вы напишете «A ();» вы явно указываете компилятору не делать этого неявно и говорите компоновщику напомнить вам, если вы когда-нибудь забудете его определить. Это относится не только к конструкторам, но и ко всем видам методов, большинство из которых не имеют естественного представления о том, что означает их неявное определение. Какое определение по умолчанию для A.makeMoney? Это нетривиально и написав A.makeMoney (); вы говорите компилятору: «поверьте, я где-нибудь определю, как это будет сделано».

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

Joseph D. 02.04.2018 09:27

... стандарт не отвечает на вопросы "почему". Предполагается, что полезное оказывается в контексте. Точно так же законодательство Великобритании не скажет вам, почему рулевое колесо должно быть с правой стороны автомобиля.

oliver 02.04.2018 09:43

спасибо Оливер. по вашему заявлению explicitely telling the compiler not to do it implicitly, and to remind you, if you should ever forget to define it., можете ссылочку поделитесь?

Joseph D. 02.04.2018 09:54

@codekaizer: Вы можете прочитать о значении объявлений в классической книге Страуструпа. Но не спрашивайте меня, на какой именно странице. Но опять же, рекомендовать эту книгу по этому единственному вопросу было бы излишним. Вы можете прочитать его почти везде в сети. Просто погуглите, например, "компоновщик конструктора по умолчанию определения объявления C++"

oliver 02.04.2018 10:03

благодарю вас! ценить это. возможно, вам нужна закладка, чтобы явно определить, что это за страница. :)

Joseph D. 02.04.2018 10:05
struct A
{
    A() = default;

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


struct A
{
  A();

Здесь вы говорите, что реализуете конструктор по умолчанию. Так что вы должны это реализовать. Например.

A::A()
: a(42)
{
}

Это дополнительная информация, дополняющая ответ, предоставленный другими.

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

Когда специальная функция-член:

  • необъявленный: не участвует в разрешении перегрузки. Чтобы использовать эту функциональность, просто не объявляйте специальную функцию-член.
  • удалено: участвует в разрешении перегрузки. Он предотвращает компиляцию, если где-то используется. Для использования этой функции используется ключевое слово delete.
  • дефолт: участвует в разрешении перегрузки. Код по умолчанию, созданный компилятором, предоставляется в качестве определения. Для использования этой функции используется ключевое слово default.

Об этом же есть интересный доклад Говарда Хинната, в котором он объясняет, когда специальные функции-члены заданы по умолчанию / удалены / необъявлены - https://thewikihow.com/video_vLinb2fgkHk

(15.1 Конструкторы)

A default constructor for a class X is a constructor of class X for which each parameter that is not a function parameter pack has a default argument (including the case of a constructor with no parameters). If there is no user-declared constructor for class X, a non-explicit constructor having no parameters is implicitly declared as defaulted (11.4). An implicitly-declared default constructor is an inline public member of its class.

Цель

A() = default;

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

struct A
{
  int a;
  A(int v): a(v) {}
};

A a; // compiler error, no default constructor

Если пользователь объявил ЛЮБОЙ конструктор, конструктор по умолчанию исчезнет.

Добавив A() = default; к этому объявлению, вы позволите создать класс A таким образом. Это явно дефолтный (11.4.2)

A function definition whose function-body is of the form = default ; is called an explicitly-defaulted definition. A function that is explicitly defaulted shall

(1.1) — be a special member function,

(1.2) — have the same declared function type (except for possibly differing ref-qualifiers and except that in the case of a copy constructor or copy assignment operator, the parameter type may be “reference to non-const T”, where T is the name of the member function’s class) as if it had been implicitly declared, and

(1.3) — not have default arguments.

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

Добавление дополнительной информации для A::A();

Ссылаясь на class.ctor / 1.2:

The class-name shall not be a typedef-name. In a constructor declaration, each decl-specifier in the optional decl-specifier-seq shall be friend, inline, explicit, or constexpr. [ Example:

struct S {
   S();      // declares the constructor
};

S::S() { }   // defines the constructor

— end example ]

Таким образом, A(); - это просто объявление, а не определение, вызывающее:

undefined reference to `A::A()'

Другие вопросы по теме