Инициализация структур в С++
В качестве дополнения к этому вопросу, что здесь происходит:
#include <string>
using namespace std;
struct A {
string s;
};
int main() {
A a = {0};
}
Очевидно, вы не можете установить std::string в ноль. Может ли кто-нибудь дать объяснение (с ссылками на стандарт С++, пожалуйста) о том, что на самом деле должно произойти здесь? А затем объясните, например):
int main() {
A a = {42};
}
Являются ли эти из них четкими?
Еще раз смущающий вопрос для меня - я всегда даю конструкторы своих структур, поэтому проблема никогда не возникала раньше.
Ответы
Ответ 1
Ваша структура является совокупностью, поэтому для нее работают обычные правила для агрегатной инициализации. Процесс описан в 8.5.1. В принципе, для этого посвящено всего 8.5.1, поэтому я не вижу причины копировать все это здесь. Общая идея практически такая же, как и в C, только что адаптированная к С++: вы берете инициализатор справа, вы берете член слева и инициализируете элемент с помощью этого инициализатора. Согласно 8.5/12, это будет инициализация копирования.
Когда вы делаете
A a = { 0 };
вы в основном копируете-инициализацию a.s
с 0
, то есть для a.s
она семантически эквивалентна
string s = 0;
Вышеприведенные компиляции, потому что std::string
можно конвертировать из указателя const char *
. (И это поведение undefined, поскольку нулевой указатель в этом случае не является допустимым аргументом.)
Ваша версия 42
не будет компилироваться по той же самой причине
string s = 42;
не будет компилироваться. 42
не является константой нулевого указателя, а std::string
не имеет средств для преобразования из типа int
.
P.S. На всякий случай: обратите внимание, что определение агрегата в С++ не является рекурсивным (в отличие от определения POD, например). std::string
не является агрегатом, но он ничего не меняет для вашего A
. A
по-прежнему является совокупностью.
Ответ 2
8.5.1/12 "Агрегаты" говорит:
Все неявные преобразования типов (раздел 4) рассматриваются при инициализации элемента агрегата с инициализатором из списка инициализаторов.
So
A a = {0};
будет инициализирован с помощью NULL char*
(как указано AndreyT и Йоханнес) и
A a = {42};
завершится с ошибкой во время компиляции, поскольку нет неявного преобразования, которое будет соответствовать конструктору std::string
.
Ответ 3
0 - константа нулевого указателя
S.4.9:
Константа нулевого указателя является интегральным постоянным выражением (5.19) rvalue целочисленного типа, которое оценивается нуль.
Константа нулевого указателя может быть преобразована в любой другой тип указателя:
S.4.9:
Константа нулевого указателя может быть преобразована в тип указателя; результатом является значение нулевого указателя этого Тип
То, что вы дали для определения A
, считается агрегатом:
S.8.5.1:
Агрегат - это массив или класс без конструкторов, не объявленных пользователем, без личных или защищенных нестатические элементы данных, базовые классы и виртуальные функции.
Вы указываете предложение инициализации:
S.8.5.1:
Когда агрегат инициализируется, инициализатор может содержать предложение инициализатора, состоящее из прилагаемой фигурной скобки, разделенный запятыми список инициализатор-предложений для членов агрегата
A
содержит член агрегата типа std::string
, и к нему применяется предложение инициализатора.
Ваш агрегат инициализируется с копией
Когда агрегат (будь то класс или массив) содержит членов типа класса и инициализируется скобкой, заключенной в initializer-list, каждый такой элемент инициализируется с копией.
Копирование инициализации означает, что у вас есть эквивалент 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
будет компилироваться (поскольку существует неявное преобразование), но приводит к поведению undefined.
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;
}
Ответ 4
Как отмечали люди, это "работает", потому что строка имеет конструктор, который может принимать 0 в качестве параметра. Если мы скажем:
#include <map>
using namespace std;
struct A {
map <int,int> m;
};
int main() {
A a = {0};
}
то мы получаем ошибку компиляции, так как класс карты не имеет такого конструктора.
Ответ 5
В 21.3.1/9 стандарт запрещает аргумент char*
соответствующего конструктора std::basic_string
быть нулевым указателем. Это должно вызывать std::logic_error
, но я еще не видел, где в стандарте есть гарантия того, что нарушение предусловия вызывает std::logic_error
.