Инициализация структур в C ++ - PullRequest
28 голосов
/ 15 мая 2010

В качестве дополнения к этот вопрос , что здесь происходит:

#include <string>
using namespace std;

struct A {
    string s;
};

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

Очевидно, вы не можете установить std :: string в ноль. Может ли кто-нибудь дать объяснение (опираясь на ссылки на стандарт C ++, пожалуйста) о том, что на самом деле должно происходить здесь? А потом объясните например):

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

Являются ли эти из них хорошо определенными?

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

Ответы [ 5 ]

29 голосов
/ 15 мая 2010

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

Когда вы делаете

A a = { 0 };

Вы в основном инициализируете копию a.s с 0, то есть для a.s это семантически эквивалентно

string s = 0;

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

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

string s = 42;

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

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

8 голосов
/ 15 мая 2010

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

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

То есть

A a = {0};

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

A a = {42};

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

3 голосов
/ 15 мая 2010

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

S.4.9:

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

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

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) называются инициализацией копирования и эквивалентны в форме Т х = а;

std::string s = 42 не скомпилируется, поскольку неявное преобразование отсутствует, std::string s = 0 скомпилируется (поскольку неявное преобразование существует), но приводит к неопределенному поведению. Конструктор std::string для const char* не определен как explicit, что означает, что вы можете сделать это: 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;
}
2 голосов
/ 15 мая 2010

Как уже указывалось, это «работает», потому что строка имеет конструктор, который может принимать 0 в качестве параметра. Если мы скажем:

#include <map>
using namespace std;

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

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

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

1 голос
/ 15 мая 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...