프로그래밍 언어/C++

[C++] 범위 (Scope)

ShovelingLife 2023. 12. 8. 17:50

블록 범위 (Block Scope)

객체의 잠재적인 범위는 블록내 선언 된 지점에서 시작하여 블록의 끝에서 끝난다. 중첩된 블럭내 상위 블럭에서 선언 된 것과 동일한 이름으로 선언하는 경우 상위에서 선언 된 것은 무시되고 블럭내 선언 된 것이 사용된다. 이런 방법은 지양해야 하며 다른 이름을 사용한다.

int main()
{
    int a = 0;      // scope of the first 'a' begins
    ++a;            // the name 'a' is in scope and refers to the first 'a'
    {
        int a = 1;  // scope of the second 'a' begins
                    // scope of the first 'a' is interrupted
        a = 42;     // 'a' is in scope and refers to the second 'a'                 
    }               // block ends, scope of the second 'a' ends
                    // scope of the first 'a' resumes
}                   // block ends, scope of the first 'a' ends
int b = a;          // Error: name 'a' is not in scope

예외 핸들러(handler)에 선언 된 이름의 잠재적 범위는 선언 시점에서 시작하여 예외 핸들러가 종료 될 때 끝난다. 다른 예외 핸들러 또는 엔클로징(enclosing, 둘러싸는) 블록은 범위에 있지 않다.

try {   
    f();
} catch(const std::runtime_error& re) { // scope of re begins
    int n = 1;                          // scope of n begins
    std::cout << re.what();             // re is in scope
}                                       // scope of re ends, scope of n ends
 catch(std::exception& e) {
    std::cout << re.what();             // error: re is not in scope
    ++n;                                // error: n is not in scope
}

if 문 또는 switch 문에서 for 문의 조건에서 선언 된 이름의 잠재적 범위, if 문의 조건에서 while 또는 switch 명령문은 선언 시점에서 시작하여 제어 명령문의 끝에서 종료된다.

Base* bp = new Derived;
if(Derived* dp = dynamic_cast<Derived*>(bp))
{
    dp->f();    // dp is in scope
}               // scope of dp ends

for(int n = 0;  // scope of n begins
    n < 10;     // n is in scope
    ++n)        // n is in scope
{
    std::cout << n << ' '; // n is in scope
}

함수 매개 변수 범위 (function parameters scope)

함수 매개 변수 (람다 표현식의 매개 변수 포함) 또는 함수 내 지역 변수의 잠재적인 범위는 선언 지점에서 시작하고 함수 정의 종료에서 끝난다. 잠재적인 범위는 function-try-block의 마지막 예외 핸들러의 끝 또는 함수 try 블록이 사용되지 않은 경우 함수 본문의 끝에서 끝난다.

const int n = 3;

int f1(int n,        // scope of global 'n' interrupted,
                     // scope of the parameter 'n' begins
       int y = n);   // error: default argument references a parameter

int (*(*f2)(int n))[n]; // OK: the scope of the function parameter 'n'
                     // ends at the end of its function declarator
                     // in the array declarator, global n is in scope
// (this declares a pointer to function returning a pointer to an array of 3 int

// by contrast
auto (*f3)(int n)->int (*)[n];  // error: parameter 'n' as array bound

int f(int n = 2)    // scope of 'n' begins
try                 // function try block
{                   // the body of the function begins
   ++n;             // 'n' is in scope and refers to the function parameter
   {
      int n = 2;    // scope of the local variable 'n' begins
                    // scope of function parameter 'n' interrupted 
      ++n;          // 'n' refers to the local variable in this block
    }               // scope of the local variable 'n' ends
                    // scope of function parameter 'n' resumes
} catch(...) {
   ++n;             // n is in scope and refers to the function parameter
   throw;
} // last exception handler ends, scope of function parameter 'n' ends
int a = n;          // OK: global 'n' is in scope

함수 범위 (function scope)

함수 내에서 선언 된 레이블은 해당 함수의 모든 위치, 모든 중첩 된 블록에서 자체 선언 전후에 적용된다.

void f()
{
    {   
        goto label; // label in scope even though declared later
label:;
    }
    goto label;     // label ignores block scope
}

void g()
{
    goto label;     // error: label not in scope in g()
}

네임스페이스 범위 (namespace scope)

네임스페이스에 선언 된 엔터티의 잠재적 범위는 선언에서 시작하며 뒤에 나오는 동일한 네임스페이스 이름에 대한 모든 네임스페이스 정의의 연결과 이름 또는 전체 네임 스페이스를 다른 범위에 도입 한 using 지시문으로 구성된다.

번역 단위의 최상위 범위 ("파일 범위"또는 "전역 범위")도 네임스페이스이며 "전역 네임스페이스 범위"라고 한다. 전역 네임스페이스 범위에서 선언 된 엔터티의 잠재적 범위는 선언에서 시작하여 번역 단위 끝까지 계속된다.

명명되지 않은 네임스페이스 또는 인라인 네임스페이스에 선언 된 엔터티의 범위에는 엔클로징 네임 스페이스가 포함된다.

namespace N { // scope of N begins (as a member of global namespace)
    int i; // scope of i begins
    int g(int a) { return a; } // scope of g begins
    int j(); // scope of j begins
    void q(); // scope of q begins
    namespace {
        int x; // scope of x begins
    } // scope of x does not end
    inline namespace inl { // scope of inl begins
      int y; // scope of y begins
    } // scope of y does not end
} // scope of i,g,j,q,inl,x,y interrupted

namespace {
    int l=1; // scope of l begins
} // scope of l does not end (it's a member of unnamed namespace)

namespace N { // scope of i,g,j,q,inl,x,y continues
    int g(char a) {  // overloads N::g(int)
        return l+a;  // l from unnamed namespace is in scope
    }
    int i; // error: duplicate definition (i is already in scope)
    int j(); // OK: repeat function declaration is allowed
    int j() { // OK: definition of the earlier-declared N::j()
        return g(i); // calls N::g(int)
    }
    int q(); // error: q is already in scope with different return type
} // scope of i,g,j,q,inl,x,y interrupted

int main() {
    using namespace N; // scope of i,g,j,q,inl,x,y resumes
    i = 1; // N::i is in scope
    x = 1; // N::(anonymous)::x is in scope
    y = 1; // N::inl::y is in scope
    inl::y = 2; // N::inl is also in scope
} // scope of i,g,j,q,inl,x,y interrupted

클래스 범위 (class scope)

클래스에 선언 된 이름의 잠재적인 범위는 선언 시점에서 시작하며 클래스 본문의 나머지 부분과 모든 함수 본문 (클래스 정의 외부 또는 이름 선언 전에 정의 된 경우에도), 기본 인수, 예외 사양을 포함한다. 클래스 내 괄호 또는 동일 이니셜 라이저 및 중첩 클래스의 모든 것들을 재귀적으로 포함한다.

class X {
    int f(int a = n) { // X::n is in scope inside default parameter
         return a*n;   // X::n is in scope inside function body
    }
    using r = int;
    r g();
    int i = n*2;   // X::n is in scope inside initializer

//  int x[n];      // Error: n is not in scope in class body
    static const int n = 1;
    int x[n];      // OK: n is now in scope in class body
};

//r X::g() {       // Error: r is not in scope outside of out-of-class member function body
auto X::g()->r {   // OK: trailing return type X::r is in scope
    return n;      // X::n is in scope in out-of-class member function body
}

이름이 선언되기 전에 클래스 본문에서 사용되며 해당 이름에 대한 또 다른 선언이 범위 내에 있으면 프로그램이 잘못 형성되어 진단할 필요가 없다.

typedef int c; // ::c
enum { i = 1 }; // ::i
class X {
    char v[i]; // Error: at this point, i refers to ::i
               // but there is also X::i
    int f() {
         return sizeof(c); // OK: X::c, not ::c is in scope inside a member function
    }
    char c; // X::c
    enum { i = 2 }; // X::i
};

typedef char* T;
struct Y {
    T a; // Error: at this point, T refers to ::T
         // but there is also Y::T
    typedef long T;
    T b;
};

모든 클래스 멤버의 이름은 네 가지 컨텍스트에서만 사용할 수 있다.

  1. 자신의 클래스 범위 또는 파생 클래스의 클래스 범위 내에서
  2. 해당 클래스의 유형 또는 클래스에서 파생 된 클래스의 표현에 적용되는 연산자
  3. -> 연산자가 클래스에 대한 포인터 유형의 표현식에 적용된 후 또는 클래스에서 파생 된 클래스에 대한 포인터
  4. :: 연산자가 해당 클래스의 이름 또는 클래스에서 파생 된 클래스의 이름에 적용

열거형 범위 (enumeration scope)

열거형으로 도입된 열거자의 이름은 선언 시점에서 시작하여 열거 지정자의 끝에 끝난다. (대조적으로, 스코프 없는 열거 자는 열거 지정자의 범위 종료 후)

enum e1_t { // unscoped enumeration
  A,
  B = A*2
}; // scope of A and B does not end

enum class e2_t { // scoped enumeration
    SA,
    SB = SA*2 // SA is in scope
}; // scope of SA and SB ends

e1_t e1 = B; // OK, B is in scope
// e2_t e2 = SB; // Error: SB is not in scope
e2_t e2 = e2_t::SB; // OK

템플릿 매개변수 범위 (template parameter scope)

템플릿 매개 변수 이름의 잠재적인 범위는 선언 시점에서 즉시 시작하여 도입 된 템플릿 선언의 가장 작은 끝까지 계속된다. 특히 템플릿 매개 변수는 후속 템플릿 매개 변수의 선언과 기본 클래스의 사양에서 사용할 수 있지만 선행 템플릿 매개 변수의 선언에서는 사용할 수 없다.

template< typename T, // scope of T begins
          T* p,       // T can be used for a non-type parameter
          class U = T // T can be used for a default type
        >
class X : public Array<T> // T can be used in base class name
{
   // T can be used inside the body as well
}; // scopes of T and U end, scope of X continues

템플릿 매개 변수 이름의 잠재적인 범위는 해당 이름이 나타나는 템플릿 매개 변수 목록 중 가장 작다.

template< template< // template template parameter
                    typename Y,     // scope of Y begins
                    typename G = Y // Y is in scope
                  > // scopes of Y and G end
          class T,
//          typename U = Y // Error: Y is not in scope
          typename U
        >
class X
{
}; // scopes of T and U end

다른 중첩 된 범위와 유사하게 템플릿 매개 변수의 이름은 자체 기간 동안 외부 범위에서 동일한 이름을 숨긴다.

typedef int N;
template< N X, // non-type parameter of type int
          typename N, // scope of this N begins, scope of ::N interrupted
          template<N Y> class T // N here is the template parameter, not int
         > struct A;

선언 지점 (point of declaration)

선언에 의해 생성된 변수 및 기타 이름의 경우 선언 지점은 해당 이름의 선언자 바로 다음이고 선언 지점은 다음과 같다.

unsigned char x = 32; // scope of the first 'x' begins
{
    unsigned char x = x; // scope of the second 'x' begins before the initializer (= x)
                         // this does not initialize the second 'x' with the value 32, 
                         // this initializes the second 'x' with its own,
                         // indeterminate, value
}
std::function<int(int)> f = [&](int n){return n>1 ? n*f(n-1) : n;};
           // the name of the function 'f' is in scope within the lambda, and can
           // be correctly captured by reference, giving a recursive function

const int x = 2; // scope of the first 'x' begins
{
    int x[x] = {}; // scope of the second x begins before the initializer (= {})
                   // but after the declarator (x[x]). Within the declarator, the outer
                   // 'x' is still in scope. This declares an array of 2 int.
}

구조화 된 바인딩의 선언 지점은 구조화 된 바인딩 선언의 식별자 목록 직후이지만 구조화 된 바인딩 초기화는 도입되는 이름 중 하나를 참조하는 것이 금지된다.(C++17 이후)

클래스 또는 템플릿의 선언 지점은 클래스 이름(또는 템플릿 특수화를 지정하는 템플릿 ID)의 식별자가 클래스 헤드에 나타나고 이미 기본 클래스 목록의 범위에 있다.

// the name 'S' is in scope immediately after it appears, 
// so it can be used in the list of base classes
struct S: std::enable_shared_from_this<S> 
{
};

열거 형 선언의 지점은 열거 지정자 또는 불투명한 열거형 선언에 나타나는 식별자 바로 뒤이다.

enum E : int { // E is already in scope
    A = sizeof(E)
};

유형 별칭 또는 별칭 템플릿의 선언 지점은 별칭이 참조하는 유형 ID 바로 다음이다.

using T = int; // point of declaration of T is at the semicolon
using T = T;   // same as T = int

열거자의 선언 지점은 그 정의 직후이다(변수에 대한 초기화 이전은 아님).

const int x = 12;
{
    enum { x = x + 1, // point of declaration is at the comma, x is initialized to 13
           y = x + 1  // the enumerator x is now in scope, y is initialized to 14
         };
}

주입 클래스 이름의 선언 지점은 클래스(또는 템플릿 클래스) 정의 시작 바로 다음이다.

template<typename T>
struct Array
// : std::enable_shared_from_this<Array> // Error: the injected class name is not in scope
   : std::enable_shared_from_this< Array<T> > //OK: the template-name Array is in scope
{ // the injected class name Array is now in scope as if a public member name
    Array* p; // pointer to Array<T>
};

 

01.02.01. 범위(Scope) - C++ 이야기(A Story of C++) (wikidocs.net)