int main() {
int i;
printf("%d", i);
}
i
как неопределённое значение (для всех намерений и целей i
не инициализирована). Обычно рекомендуется инициализировать переменные, когда они определены, например int i = 0
;, и переменные всегда следует инициализировать перед использованием. Независимо от того, сколько раз 0
.int i;
int main() {
printf("%d", i);
}
i
— она может быть любой.struct A {
int i;
};
int main() {
struct A a;
printf("%d", a.i);
}
a
не инициализирована. Мы увидим предупреждение при компиляции.$ gcc -Wuninitalized a.c
a.c: In function ‘main’:
a.c:9:5: warning: ‘a.i’ is used uninitialized in this function [-Wuninitialized]
printf("%d\n", a.i);
struct A {
int i;
} const default_A = {0};
void init_A(struct A *ptr) {
ptr->i = 0;
}
int main() {
/* helper function */
struct A a1;
init_A(&a1);
/* during definition;
* Initialize each member, in order.
* Any other uninitialized members are implicitly
* initialized as if they had static storage duration. */
struct A a2 = {0};
/* Error! (Well, technically) Initializer lists are 'non-empty' */
/* struct A a3 = {}; */
/* ...or use designated initializers if C99 or later */
struct A a4 = {.i = 0};
/* default value */
struct A a5 = default_A;
}
0
.struct A {
int i;
};
int main() {
A a;
std::cout << a.i << std::endl;
}
A
, значение которого может быть любым. В C++ a
инициализирована по умолчанию, то есть для построения структуры используется конструктор по умолчанию. Поскольку A
настолько тривиальна, у неё неявно определённый конструктор по умолчанию, который в этом случае ничего не делает. Неявно определенный конструктор по умолчанию «имеет точно такой же эффект», как:struct A {
A(){}
int i;
}
g++ 8.2.1
выдавал хорошие предупреждения, а clang++ 7.0.1
в этом случае ничего не выдавал (с установленным -Wuninitialized
). Обратите внимание, что включена оптимизация для просмотра дополнительных примеров.$ g++ -Wuninitalized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:9:20: warning: ‘a.A::i’ is used uninitialized in this function [-Wuninitialized]
std::cout << a.i << std::endl;
A::i
?struct A {
int i;
};
int main() {
A a = {.i = 0};
std::cout << a.i << std::endl;
}
$ g++ -Wuninitialized -O2 -pedantic-errors a.cpp
a.cpp: In function ‘int main()’:
a.cpp:9:12: error: C++ designated initializers only available with -std=c++2a or -std=gnu++2a [-Wpedantic]
A a = {.i = 0};
-pedantic-errors
для удаления поддержки нестандартных расширений gcc.struct A {
int i;
};
int main() {
A a = {0};
std::cout << a.i << std::endl;
}
$ g++ -Wuninitialized -O2 -pedantic-errors a.cpp
$
A a = {};
с тем же эффектом, что и нулевая инициализация a.i
. Это потому что A
представляет собой агрегированный тип. Что это такое?A
, он будет рекурсивно инициализирован определённым значением. Если у нас встроенный объект, как int i
, то он инициализируется нулём.А
методом C++ с конструкторами (торжественная музыка)! Можем назначить элементу i
в структуре А
начальное значение в пользовательском конструкторе по умолчанию:struct A {
A() : i(0) {}
int i;
};
i
в списке инициализаторов членов. Более грязный способ — установить значение внутри тела конструктора:struct A {
A() { i = 0; }
int i;
};
В C++11 и более поздних версиях можно использовать дефолтные инициализаторы членов (серьёзно, по возможности просто используйте их).
struct A { int i = 0; // default member initializer, available in C++11 and later };
i
установлен в 0, когда любая структура A
инициализируется по умолчанию. Наконец, если мы хотим разрешить пользователям A задать начальное значение i
, можно для этого создать другой конструктор. Или смешать их вместе с аргументами по умолчанию:struct A {
A(int i = 0) : i(i) {}
int i;
};
int main() {
A a1;
A a2(1);
std::cout << a1.i << " " << a2.i << std::endl;
}
$ g++ -pedantic-errors -Wuninitialized -O2 a.cpp
$ ./a.out
0 1
Примечание. Нельзя написатьA a();
для вызова конструктора по умолчанию, потому что он будет воспринят как объявление функции с именемa
, которая не принимает аргументов и возвращает объектA
. Почему? Потому что кто-то когда-то давно хотел разрешить объявления функций в блоках составных операторов, и теперь мы с этим застряли.
gnu++1y
, что эквивалентно C++14 с некоторыми дополнительными расширениями GNU. Более того, эта версия g++ также полностью поддерживает C++17. «Разве это имеет значение?» — можете вы спросить. Парень, надевай свои рыболовные сапоги и следуй за мной в самую гущу.Единообразная инициализация C++11 не является абсолютно единообразной, но это почти так.
{thing1, thing2, ...}
, это называется braced-init-list) и выглядит следующим образом:#include <iostream>
struct A {
int i;
};
int main() {
A a1; // default initialization -- as before
A a2{}; // direct-list-initialization with empty list
A a3 = {}; // copy-list-initialization with empty list
std::cout << a1.i << " " << a2.i << " " << a3.i << std::endl;
}
$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:9:26: warning: ‘a1.A::i’ is used uninitialized in this function [-Wuninitialized]
std::cout << a1.i << " " << a2.i << " " << a3.i « std::endl;
a1.i
. Очевидно, что список инициализации работает иначе, чем просто вызов конструктора.A a{};
производит то же поведение, что и A a = {};
. В обоих случаях a
инициализируется пустым списком braced-init-list. Кроме того, A a = {};
больше не называется агрегатной инициализацией — теперь это copy-list-initialization (вздыхает). Мы уже говорили, что A a;
создаёт объект с неопределённым значением и вызывает конструктор по умолчанию.A
приводит ко второму пункту.i
, равного 0.int main() {
A a1{0};
A a2{{}};
A a3{a1};
std::cout << a1.i << " " << a2.i << " " << a3.i << std::endl;
}
$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp
$
a1.i
инициализируется в 0, a2.i
инициализируется пустым списком, а a3
— копия, построенная из a1
. Вы ведь знаете, что такое конструктор копий, верно? Тогда вы знаете также о конструкторах перемещения, ссылках rvalue, а также передаваемых ссылках, pr-значениях, x-значениях, gl-значе… ладно, неважно.A
не является агрегатным типом?using Base::Base;
, в C++17)#include <iostream>
struct A {
A(){};
int i;
};
int main() {
A a{};
std::cout << a.i << std::endl;
}
$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:8:20: warning: ‘a.A::i’ is used uninitialized in this function [-Wuninitialized]
std::cout << a.i << std::endl;
A
есть предоставленный пользователем конструктор, поэтому инициализация списка работает иначе.A
приводит ко второму пункту.a.i
не инициализируется.Что такое конструктор, предоставленный пользователем?
struct A { A() = default; };
Это не конструктор, предоставленный пользователем. Это как если вооще не объявлено никакого конструктора, аA
является агрегатом.
struct A { A(); }; A::A() = default;
Вот это конструктор, предоставленный пользователем. Это словно мы написалиA(){}
в теле, гдеА
не является агрегатом.
И угадайте что? В C++20 формулировка изменилась: теперь она требует, чтобы у агрегатов не было объявленных пользователем конструкторов :). Что это означает на практике? Я не уверен! Давайте продолжим.
#include <iostream>
class A {
int i;
friend int main();
};
int main() {
A a{};
std::cout << a.i << std::endl;
}
A
— это класс, а не структура, поэтому i
будет приватным, и нам пришлось установить main
в качестве дружественной функции. Что делает А
не агрегатом. Это просто обычный тип класса. Это значит, что a.i
останется неинициализированным, верно?$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp
$
a.i
инициализируется как 0, даже если не вызывает инициализацию агрегата:#include <iostream>
class A {
int i;
friend int main();
};
int main() {
A a = {1};
std::cout << a.i << std::endl;
}
$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:7:13: error: could not convert ‘{1}’ from ‘<brace-enclosed initializer list>’ to ‘A’
A a = {1};
A
не является агрегатом, поэтому происходит следующее:1
в A
, компиляция завершается ошибкой.#include <iostream>
struct A {
A(int i) : i(i) {}
A() = default;
int i;
};
int main() {
A a{};
std::cout << a.i << std::endl;
}
$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp
$
#include <iostream>
struct A {
A(){}
int i;
};
struct B : public A {
int j;
};
int main() {
B b = {};
std::cout << b.i << " " << b.j << std::endl;
}
$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:11:25: warning: ‘b.B::<anonymous>.A::i’ is used uninitialized in this function [-Wuninitialized]
std::cout << b.i << " " << b.j << std::endl;
b.j
инициализируется, а b.i
нет. Что происходит в этом примере? Не знаю! Все базы b
и члены здесь должны получить нулевую инициализацию. Я задал вопрос на Stack Overflow, и std::initializer_list
. У него собственный тип: очевидно, std::initializer_list<T>
. Вы можете создать его с помощью braced-init-list. И кстати, braced-init-list для списка инициализации не имеет типа. Не путайте initializer_list со списком инициализации и braced-init-list! Все они имеют отношение к спискам инициализаторов членов и инициализаторам членов по умолчанию, так как помогают инициализировать нестатические элементы данных, но при этом сильно отличаются. Они связаны, но разные! Несложно, правда?struct A {
template <typename T>
A(std::initializer_list<T>) {}
int i;
};
int main() {
A a1{0};
A a2{1, 2, 3};
A a3{"hey", "thanks", "for", "reading!"};
std::cout << a1.i << a2.i << a3.i << std::endl;
}
$ g++ -std=c++17 -pedantic-errors -Wuninitialized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:12:21: warning: ‘a1.A::i’ is used uninitialized in this function [-Wuninitialized]
std::cout << a1.i << a2.i << a3.i << std::endl;
^
a.cpp:12:29: warning: ‘a2.A::i’ is used uninitialized in this function [-Wuninitialized]
std::cout << a1.i << a2.i << a3.i << std::endl;
^
a.cpp:12:37: warning: ‘a3.A::i’ is used uninitialized in this function [-Wuninitialized]
std::cout << a1.i << a2.i << a3.i << std::endl;
A
один шаблонный конструктор, который принимает std::initializer_list<T>
. Каждый раз вызывается конструктор, предоставляемый пользователем, что ничего не делает, поэтому i
остаётся неинициализированным. Тип T
выводится в зависимости от элементов в списке, а новый конструктор создаётся в зависимости от типа.{0}
выводится как std::initializer_list<int>
с одним элементом 0
.{1, 2, 3}
выводится как std::initializer_list<int>
с тремя элементами.std::initializer_list<const char*>
с четырьмя элементами.Примечание:A a{}
приведёт к ошибке, так как тип не может быть выведен. Например, нам нужно написатьa{std::initializer_list<int> {}}
. Или мы можем точно указать конструктор, как вA(std::initializer_list<int>){}
.
std::initializer_list
действует примерно как типичный контейнер STL, но только с тремя компонентными функциями: size
, begin
и end
. Итераторы begin
и end
вы можете нормально разыменовать, увеличивать и сравнивать. Это полезно, когда требуется инициализировать объект списками разной длины:#include <vector>
#include <string>
int main() {
std::vector<int> v_1_int{5};
std::vector<int> v_5_ints(5);
std::vector<std::string> v_strs = {"neato!", "blammo!", "whammo!", "egh"};
}
std::vector<T>
есть конструктор, который принимает std::initializer_list<T>
, поэтому мы можем легко инициализировать векторы, как показано выше.Примечание. Векторv_1_int
создан из его конструктора, который берётstd::initializer_list<int< init
с одним элементом5
.
Векторv_5_ints
создан из конструктораsize_t count
, который инициализирует вектор из (5
) элементов и инициализирует их в значения (в данном случае все равны0
).
#include <iostream>
struct A {
A(std::initializer_list<int> l) : i(2) {}
A(int i = 1) : i(i) {}
int i;
};
int main() {
A a1;
A a2{};
A a3(3);
A a4 = {5};
A a5{4, 3, 2};
std::cout << a1.i << " "
<< a2.i << " "
<< a3.i << " "
<< a4.i << " "
<< a5.i << std::endl;
}
std::initializer_list<int>
, а другой с аргументами по умолчанию принимает int
. Прежде чем посмотреть на выдачу ниже, попробуйте сказать, каким будет значение для каждого i
.$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp
$ ./a.out
1 1 3 2 2
a1
всё должно быть легко. Это простая инициализация по умолчанию, которая выбирает конструктор по умолчанию, используя его аргументы по умолчанию. a2
использует список инициализации с пустым списком. Поскольку у A
есть конструктор по умолчанию (с аргументами по умолчанию), происходит инициализация значения с простым обращением к этому конструктору. Если бы у A
не было этого конструктора, то пошло бы обращение к конструктору в третьей строке с вызовом пустого списка. a3
использует скобки, а не список braced-init-list, поэтому разрешение перегрузки выбирает 3
с конструктором, принимающим int
. Далее, а4
использует список инициализации, для которого разрешение перегрузки склоняется в пользу конструктора, принимающего объект std::initializer_list
. Очевидно, a5
нельзя соотнести с каким-то int
, поэтому используется тот же конструктор, что и для a4
.this
и коллизиях идентификаторов в C.И это всё, что я могу сказать об этом.
Source: https://habr.com/ru/post/438492/