Инициализация структур в C++

Как приложение к этому вопросу, что продолжается здесь:

#include 
using namespace std;

struct A {
    string s;
};

int main() {
    A a = {0};
}

Очевидно, Вы не можете установить станд.:: представьте в виде строки для обнуления. Кто-то может дать объяснение (поддержанный ссылками на Стандарт C++) о том, что, как на самом деле предполагается, происходит здесь? И затем объясните, например):

int main() {
    A a = {42};
}

Действительно ли любой из них четко определен?

Еще раз смущающий вопрос для меня - я всегда даю моим конструкторам структур, таким образом, проблема никогда не возникала прежде.

28
задан Community 23 May 2017 в 12:18
поделиться

5 ответов

Ваша структура является агрегатом , поэтому для него работают обычные правила агрегатной инициализации. Процесс описан в 8.5.1. По сути, ему посвящена вся версия 8.5.1, поэтому я не вижу смысла копировать все это здесь. Общая идея практически такая же, как и в C, но адаптирована к C ++: вы берете инициализатор справа, вы берете член слева, и вы инициализируете член этим инициализатором. Согласно 8.5 / 12, это должна быть инициализация копирования .

Когда вы выполняете

A a = { 0 };

, вы в основном инициализируете как с 0 , то есть для как это семантически эквивалентно

string s = 0;

Выше компилируется, потому что std :: string конвертируется из указателя const char * . (И это неопределенное поведение, поскольку нулевой указатель не является допустимым аргументом в этом случае.)

Ваша версия 42 не будет компилироваться по той же причине, по которой

string s = 42;

не будет компилироваться. 42 не является константой нулевого указателя, а std :: string не имеет средств для преобразования из типа int .

P.S. На всякий случай: обратите внимание, что определение агрегата в C ++ не является рекурсивным (в отличие, например, от определения POD). std :: string не является агрегатом, но он ничего не меняет для вашего A . A по-прежнему является агрегатом.

29
ответ дан 28 November 2019 в 03:26
поделиться

В 21.3.1 / 9 стандарт запрещает аргументу char * соответствующего конструктора std :: basic_string быть нулевым указателем. Это должно вызвать ошибку std :: logic_error , но мне еще предстоит увидеть, где в стандарте гарантируется, что нарушение предварительного условия вызывает ошибку std :: logic_error .

1
ответ дан 28 November 2019 в 03:26
поделиться

8.5.1 / 12 «Агрегаты» говорят:

Все неявные преобразования типов (пункт 4) учитываются при инициализации составного члена с помощью инициализатора из списка инициализаторов.

Таким образом,

A a = {0};

будет инициализирован NULL char * (как указано AndreyT и Johannes ), а

A a = {42};

завершится ошибкой во время компиляции поскольку не существует неявного преобразования, которое соответствовало бы конструктору std :: string .

8
ответ дан 28 November 2019 в 03:26
поделиться

0 является константой нулевого указателя

S.4.9:

Константа нулевого указателя - это интегральное постоянное выражение (5.19) rvalue целого типа, которое оценивается в нулю.

Константа нулевого указателя может быть преобразована в любой другой тип указателя:

S.4.9:

Константа нулевого указателя может быть преобразована в тип указателя; результатом будет значение нулевого указателя этого типа

Что вы дали? типа

То, что вы дали для определения A, считается агрегатом:

S.8.5.1:

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

Вы указываете условие инициализации:

S.8.5.1:

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

A содержит член агрегата типа std::string, и к нему применяется условие инициализации.

Ваш агрегат инициализирован копией

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

Копирующая инициализация означает, что вы имеете эквивалент std::string s = 0 или std::string s = 42;

S.8.5-12

Инициализация, которая происходит при передаче аргументов, возврате функции, выбрасывании исключения (15.1), обработке исключение (15.3), и закрытых скобками списках инициализаторов (8.5.1), называется копирующей инициализацией и эквивалентна форме T x = a;

std::string s = 42 не будет компилироваться, потому что неявное преобразование отсутствует, std::string s = 0 будет компилироваться (потому что неявное преобразование существует), но приведет к неопределенному поведению.

конструктор std::string для const char* не определен как явный, что означает, что вы можете сделать следующее: std::string s = 0

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

class mystring
{
public:

  explicit mystring(const char* p){}
};

struct A {
  mystring s;
};


int main()
{
    //Won't compile because no implicit conversion exists from const char*
    //But simply take off explicit above and everything compiles fine.
    A a = {0};
    return 0;
}
3
ответ дан 28 November 2019 в 03:26
поделиться

Как отмечают люди, это "работает", потому что у string есть конструктор, который может принимать 0 в качестве параметра. Если мы скажем:

#include <map>
using namespace std;

struct A {
    map <int,int> m;
};

int main() {
    A a = {0};
}

то получим ошибку компиляции, так как класс map не имеет такого конструктора.

2
ответ дан 28 November 2019 в 03:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: