Разница в видимости членов класса и обычных переменных
#include <iostream>
struct S {
int m = 41;
S(int m = 42): m{m} { std::cout << m << '\n'; }
};
int main() {
int m = 41;
{ int m = m; std::cout << m << '\n'; } // (1) not 41
S{}; // (2) 42
}
Почему в 1 случае не используется значение 41 внешней переменной, а во втором используется внешний параметр 42? Какие правила Стандарта определяют, что int m = m; и m{m} работают именно так?
Ответы (2 шт):
Во втором случае int m в пределах блока скрывает переменную int m вне его.
В первом - это инициализация, так что первое m относится к члену-данным структуры, а второе — локальная переменная (переданная как параметр).
А вот о конкретных местах стандарта — это надо искать...
Поиск имён для инициализируемых членов в списке инициализации конструктора не учитывает область видимости параметров конструктора.
mem-initializer:
mem-initializer-id ( expression-listopt)
mem-initializer-id braced-init-listLookup for an unqualified name in a mem-initializer-id ignores the constructor's function parameter scope.
C другой стороны, поиск имён для инициализаторов в списке инициализации конструктора начинается в области видимости параметров конструктора.
The expression-list or braced-init-list of a mem-initializer is in the function parameter scope of the constructor and can use this to refer to the object being initialized.
class X {
int a;
int b;
int i;
int j;
public:
const int& r;
X(int i): r(a), b(i), i(i), j(this->i) { }
};
initializes
X::rto refer toX::a, initializesX::bwith the value of the constructor parameteri, initializesX::iwith the value of the constructor parameteri, and initializesX::jwith the value ofX::i; this takes place each time an object of classXis created.
Поэтому здесь
struct S {
int m = 41;
S(int m = 42): m{m} { std::cout << m << '\n'; }
};
член m структуры S инициализируется параметром m конструктора S.
Поведение конструкции
int m = 41;
{int m = m;}
описывается другим разделом стандарта языка.
basic.scope.pdecl, Point of declaration:
1 The locus of a declaration ([basic.pre]) that is a declarator is immediately after the complete declarator ([dcl.decl]).
[Example 1:
unsigned char x = 12;
{ unsigned char x = x; }
Here, the initialization of the secondxhas undefined behavior, because the initializer accesses the secondxoutside its lifetime ([basic.life]). — end example]
2 [Note 1: A name from an outer scope remains visible up to the locus of the declaration that hides it.
[Example 2:
const int i = 2;
{ int i[i]; }
declares a block-scope array of two integers. — end example]
— end note]
После того как переменная объявлена {int m ...}, она скрывает объявление во внешней области видимости int m = 41;.
Так как время жизни объекта начинается после его инициализации, то здесь
{int m = m;}
мы пытаемся прочитать значение объекта, который ещё не существует. Поведение программы не определено.