Подтвердить что ты не робот

Разница между вызовами конструктора с и без()

Я начинаю С++ и хочу понять, почему

return std::list<int>();

нужны скобки, но

std::list<int> foo;

не требуется скобок. Какая разница между этими конструкторскими вызовами?

4b9b3361

Ответ 1

Ни один из них не является вызовом конструктора.

Первым является явное преобразование типов, которое создает объект типа std::list<int>.

Второе - это определение переменной, которое создает объект типа std::list<int>.

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

Хотя вы можете видеть, что такие вещи упоминаются как "вызовы конструктора", нет синтаксической конструкции для явного и однозначного вызова конструктора в С++.

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


Обратите внимание, что если вы добавите круглые скобки во второй пример, вы фактически объявляете функцию, а не определяете переменную:

std::list<int> foo; //variable definition
std::list<int> foo(); //function taking no args, returning a std::list<int>

Это обычно называется most-vexing-parse. С++ 11 ввел исправленную инициализацию, чтобы обойти это:

std::list<int> foo{}; //variable definition

Стандарт, для тех, кто склонен

(Цитаты из N3337)

"Но T() обязательно выглядит как вызов конструктора, почему это не так?"

В этом контексте T() называется явным преобразованием типа с функциональными обозначениями:

5.2.3 Явное преобразование типа (функциональная нотация) [expr.type.conv]

1 [...]

2 Выражение T(), где T - спецификатор простого типа или typename-specifier для типа объекта без массива или (возможно, cv-qualified) тип void, создает значение prvalue указанного типа, который инициализируется значением (8.5; инициализация не выполняется для случая void()). [Примечание: если T - тип неклассов, который cv-qualified, cv-квалификаторы игнорируются при определении типа полученного prvalue (3.10). -end note]

Таким образом, это создает значение prvalue, которое инициализируется значением.

[dcl.init]/7: Для инициализации объекта с типом T означает:

- , если T - тип класса (возможно, cv-grade) (раздел 9) с предоставленным пользователем конструктором (12.1), тогда конструктор по умолчанию для T называется (и инициализация плохо сформирован, если T не имеет доступных значений по умолчанию Конструктор);

- [...]

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

[class.ctor]/1: Конструкторы не имеют имен. Для объявления или определения конструктора используется специальный синтаксис декларатора. Синтаксис использует:

- необязательный spec-specifier-seq, в котором каждый spec-спецификатор является либо спецификатором функции, либо constexpr,

- имя класса конструкторов и

- список параметров

в этом порядке. В таком объявлении необязательные круглые скобки вокруг имени класса конструктора игнорируются.

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

"Это похоже на академическое различие, действительно ли это на практике?"

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

Зачем использовать неточный synecdoche, когда у нас есть формальные термины, которые позволяют избежать путаницы?

Ответ 2

посмотрите на это следующим образом:
1) вам нужно создать объект
2) вам нужно вернуть его.

скажем, что компилятор смотрит на выражение return Foo;, компилятор думает: "Эй, он хочет, чтобы я возвращал тип! Тип - это не то, что я могу вернуть! Мне нужна настоящая переменная здесь!"

чтобы вы могли написать что-то вроде

Foo temp;
return temp;

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

выглядит ли код return createDefaultFoo(); более разумным? ну, это то, что делает Foo(), он создает и возвращает анонимный Foo obejct

в этой строке:

std::list<int> foo;

компилятор может сказать, что вам нужен объект с именем Foo от типа std::list<int>. поэтому () redundand. как сказано здесь, добавление () заставит компилятор считать, что вы объявляете функцию.

Ответ 3

Оба оператора ссылаются на конструктор по умолчанию.

return std::list<int>();

Это то же самое, что:

std::list<int> value;
return value;

Здесь создается объект (с использованием конструктора по умолчанию) и возвращается объект.

std::list<int> foo;

Здесь объект foo создается с использованием конструктора по умолчанию.

Вот еще один способ сделать то же самое в C++11:

std::list<int> foo;
std::list<int> foo1{}; // C++11