레이블이 scope인 게시물을 표시합니다. 모든 게시물 표시
레이블이 scope인 게시물을 표시합니다. 모든 게시물 표시

2017-08-02

[C++] 이름과 스코프

 C++에서는 많은 것들이 이름을 가질 수 있다. 변수는 모두 이름을 가지고, 객체는 변수를 통해서 이름을 가질 수 있다. 타입도 이름이 있고, 함수도 이름이 있다. 람다 함수는 익명 함수라는 번역 그대로 이름이 없지만, 변수에 할당하여 이름을 통해 접근할 수도 있다.
 이 이름을 어디서부터 어디까지 사용할 수 있는가를 그 이름의 스코프라고 하는데, C++은 상황에 따라 다른 스코프를 사용한다. 이번 글에서는 C++의 이름이 가지는 스코프에 관해서 설명하도록 하겠다.

block scope

 block scope는 선언된 문장부터 선언된 block이 끝날 때까지 사용할 수 있다. block 밖에서 선언된 같은 이름이 있으면 이름이 선언되기 전까지는 block 밖에서 선언된 이름을 그대로 가지고, 선언된 문장부터 새로 선언한 의미를 가진다.
 예를 들어 위와 같은 코드가 있을 때 4번째 줄까지 a는 2번째 줄에서 선언된 a이지만, 4번째 줄부터 block이 끝나는 10번째 줄까지는 4번째 줄에서 선언된 a이다. 또한, block scope를 가지는 이름은 같은 block 안에서 다른 의미를 가지기 위해 다시 사용될 수 없다. 즉, 위에서 5번째 줄에서 10번째 줄까지 사이에서는 a를 다른 의미로 사용할 수 없다.
 스코프를 가지는 이름의 대표적인 예제로는 함수 안에서 선언된 변수인 지역 변수(local variable)가 있다. 이때 지역 변수와 automatic storage에 저장되는 지역 객체를 혼동하면 안 된다. 스코프는 어디까지나 컴파일 시 그 이름을 어디까지 사용할 수 있는가 하는 것이지 객체의 라이프타임과는 상관없다. 지역 변수 중에서도 static이나 thread_local 같은 storage를 지정해주는 specifier가 붙은 경우 이 객체들은 automatic storage에 저장되지 않는다.
 지역 변수 외에도 typedefusing으로 선언되는 type alias, 함수 안에서 선언되는 class와 enum 같은 경우도 함수 안에서 선언됐다면 block scope를 가진다.
 함수의 인자, for 문에서 선언된 변수, catch 된 exception, C++17이라면 if control 문에서 선언된 변수도 block scope를 가지는데, 이들은 해당 문장이 있는 block이 아닌, 문장 다음에 있는 block에 종속된다.

namespace scope

 block scope보다 약간 큰 범위를 가지는 것은 namespace scope다. namespace scope는 이름 그대로 namespace에 선언된 모든 이름을 의미한다. 당연히 이 namespace에는 global namespace도 포함되므로 outermost namespace 밖에 선언된 이름도 namespace scope를 가진다. namespace scope를 가지는 이름은 block scope를 가지는 이름과 마찬가지로 선언된 문장부터 접근할 수 있다. 하지만 block scope와는 다르게 선언된 namespace가 끝나더라도 접근 불가능하지 않다. 아래와 같이 ::(scope resolution operator)를 통해서 접근할 수 있다.

enumeration scope

 enumeration scope는 C++11에 처음 들어간 기능으로 enum class가 들어가면서 같이 들어갔다. C++11의 enum class는 scoped enum이라고 부르기도 하는데, 이는 enumerator의 이름이 enumeration에 한정되기 때문이다.  같은 스코프 내에서 선언된 이름을 다시 선언할 수 없다는 규칙은 enumeration scope도 마찬가지므로 enumerator의 이름을 중복해서 사용할 수 없다. 하지만 enum class가 선언된 block scope나 namespace scope의 이름을 더럽히지 않기 때문에 해당 스코프에는 중복되는 이름이 있어도 괜찮다. enumerator를 사용할 때는 enumeration의 이름과 합쳐서 scope resolution operator를 사용한다.

function scope

 function hoisting이란 선언된 이름이 마치 함수의 시작 부분으로 옮겨진 것처럼 해석되는 경우를 말한다. JavaScript에서는 모든 변수가 function hoisting 되므로 JavaScript를 써본 사람이라면 익숙한 개념일 것이다. 하지만 마치 JavaScript의 특이한 기능인 것처럼 알려졌기 때문에 C++에서도 function scope에는 function hoisting이 사용된다.
 사실 function scope를 사용하는 이름은 label 딱 한 경우뿐이고, 대부분의 경우 goto를 사용하는 것을 피하므로 사용할 일이 거의 없다. 어찌 됐든 이 라벨은 같은 함수 안에서 선언됐다면, 그 선언된 위치와 상관없이 어디서나 라벨을 사용할 수 있다.

class scope

 class scope는 클래스 정의 안에서 선언된 모든 이름이 가지는 스코프다. 같은 클래스 안에서 선언됐다면, 클래스의 멤버 변수나 멤버 함수는 물론이고, static 멤버, static 함수, nested class나 class 안에서 정의된 enumeration과 타입들도 모두 같은 class scope로 묶인다.
 이때 function scope처럼 모든 이름이 hoisting 된 것으로 해석된다. 예를 들어 아래와 같은 함수가 있을 때, S::f 함수 안에서 i를 사용할 때, 아직 S::i는 선언되지 않았기에 func 함수에 지역 변수로 선언된 i를 의미하는 것 같지만, 실제로는 S 클래스의 모든 변수 선언이 hoisting 됐기 때문에 클래스 S 안에서 사용되는 i는 모두 S::i를 의미한다.

function prototype scope

 위에서 이미 함수의 인자는 block scope를 가진다고 말했기 때문에 function prototype scope가 따로 존재한다는 것이 이상할 수 있다. 하지만 이 이름을 잘 보면 답을 알 수 있다. function prototype scope는 function declarator에 한정되는 scope이다. function prototype scope에 해당하는 이름. 즉, 함수의 파라미터는 선언된 순간부터 function declarator가 끝나는 순간까지 사용할 수 있다.  따라서 위와 같은 코드가 있을 때 f 함수의 인자로 선언된 abf의 선언이 끝날 때까지 function prototype scope를 가지고, function body에서는 block scope로 사용된다. 이때 선언된 순간부터 사용할 수 있으므로, a의 기본값으로 사용된 b는 첫 번째 줄에서 선언된 constexpr intb이다.  하지만 위와 같은 코드에서는 b의 기본값으로 a를 사용하기 전에 이미 a를 인자로 선언했기 때문에 첫 번째 줄에서 선언한 a가 아닌 첫 번째 인자인 a가 된다. 이때 a가 무엇인지 컴파일 타임에 알 수 없으므로 이는 컴파일 에러를 발생시킨다.
 하지만 아래처럼 컴파일 타임에 값을 알 수 있는 경우에는 사용할 수 있다.  이때 sizeof에 사용된 a는 첫 번째 줄에서 사용된 int32_t가 아닌, 첫 번째 인자인 int8_t 이므로 기본값은 1이 된다.

template parameter scope

 마지막으로 설명할 스코프는 template parameter scope다. 이름 그대로 template parameter의 스코프를 정해주기 위한 것으로 선언된 순간부터 접근 가능하며, 선언을 포함한 가장 작은 template의 구현이 끝나는 시점까지다. template parameter scope인 이름은 해당하는 스코프가 끝날 때까지 다른 이름으로 사용할 수 없다. 즉, template 함수나 template 클래스 안에서 template parameter의 이름을 다른 이름으로 사용할 수 없다.
 하지만 template parameter scope 안에 다른 template parameter scope가 있을 때, 바깥 스코프에서 선언된 이름을, 안쪽 스코프에서 다시 선언하여 사용할 수는 있다.

 지금까지 C++이 가지는 스코프의 종류에 관해서 설명하였다. 요약하자면 C++은 block scope, namespace scope, enumeration scope, function scope, class scope, function prototype scope, template parameter scope의 7가지 스코프를 가지고 있으며, 이 중 class scope와 function scope는 선언한 위치에 상관없이 스코프 내에서 모든 언제나 의미를 가지지만, 나머지 5개 스코프는 이름이 선언된 순간부터 의미를 가진다. 또한, namespace scope, enumeration scope, class scope의 경우 스코프 밖에서도 ::(scope resolution operator)를 통해서 접근할 수 있다.

2015-08-25

[ECMAScript 6] block 안에서 함수 만들기

 JavaScript 함수 선언의 가장 큰 특징은 함수의 선언 위치에 상관없이 언제나 코드의 가장 위에서 함수를 선언한 것처럼 코드가 실행된다는 것이다. 따라서 아래와 같은 코드는

 사실은 아래와 같은 코드라고 보아도 된다.

 이를 function hoisting이라고 한다. 이 덕분에 함수 선언문보다 앞에서 함수를 사용할 수 있다. 하지만 함수 선언은 언제나 스코프의 가장 윗부분으로 hoisting 된다. 따라서 함수 안에서 선언된 함수는 함수 내에서 언제나 같은 함수를 의미했고, 특정 block 안에서는 다른 함수를 의미하도록 사용할 수 없었다.

 하지만 ECMAScript 6에서는 block 단위의 함수 선언을 허용한다.

 즉, 위와 같이 if block 안에서만 다른 값을 의미하도록 하는 것이 가능하다.

 하지만 아쉽게도 이는 아직 대부분 브라우저나 node.js에서는 구현되지 않았다. 따라서 블록 단위 함수 선언을 사용하려면 babel.js를 사용해야 한다.

2015-07-19

[ECMAScript 6] const - 상수 선언하기

 constlet과 같이 ECMAScript 6에 도입된 block 단위 상수 선언문이다.
 중복으로 선언할 수 없고, 선언 전에 사용할 수 없다는 것은 let과 같다. 거기에 const는 추가적인 제약이 더 붙는다.

 우선 const로 선언된 이름에는 값을 재할당할 수 없다. 이는 문법적으로 에러로 처리한다. 따라서 const에 값을 할당하는 구문을 실행할 때 발생하는 것이 아니라 const에 값을 할당하는 구문이 있는 함수가 선언될 때 에러가 발생한다.

 또한, const를 이용해서 상수를 선언할 때는 언제나 값을 초기화해주어야 한다. const로 선언된 상수에 값을 할당하지 못한다는 것을 생각하면, 당연한 일이다. 이 또한 문법 에러로, 초기화하지 않는 const를 선언할 때가 아니라, 선언하는 구문이 있는 함수를 선언할 때 에러가 발생한다.

 하지만 const도 상수 선언을 위한 완벽한 해결책은 아니다. const로 선언한 상수에는 값을 재할당할 수 없지만, 상수임에도 불구하고 값을 변경시킬 수 있기 때문이다. const로 선언한 상수는 어디까지나 값의 재할당을 막을 뿐, 그 값을 보호해주지 않는다.

 const가 완벽한 해결책인 것은 아니지만, 한계를 알고 적절하게 사용하면 좀 더 안정적이고 가독성 있는 코드를 작성할 수 있다.

2015-07-18

[ECMAScript 6] let - block 단위 스코프

 ECMAScript 6에서는 기존의 function scope였던 var 이외에 let이라는 block scope 변수 선언을 지원한다. let을 통한 변수 선언은, var를 통한 변수 선언과 다르게 hoisting 하지 않는다. 즉, 변수가 선언된 이후부터 변수가 유효하고 그전에는 해당 변수를 사용할 수 없다.


 hoisting을 없앤 것뿐 아니라 그 외의 실수하기 쉬운 부분을 에러로 처리하여 좀 더 안전한 코드를 작성할 수 있도록 하였다.

 예를 들면, ECMAScript 5에서는 hoisting 된 변수를 실수로 선언 전에 사용할 경우 그 변수는 undefined가 된다. 하지만 let을 사용하면, 변수를 hositing 하지 않을 뿐 아니라, 변수를 선언한 블록 안에서 선언 전에 해당하는 이름을 사용하는 것을 에러로 처리한다.

 또한, 이전에는 같은 스코프, 다시 말해서 같은 함수 안에서 변수의 선언문이 여러 개 있는 것이 정상적인 구문으로 처리되었다. 하지만 let을 사용한다면 같은 스코프에서 중복으로 선언하는 것이 에러로 처리된다.

 아쉬운 점은 위와 같은 에러가 함수의 선언에서 발생하는 것이 아니라, 실제로 그 구문을 실행할 때 발생한다는 것이다. 따라서 여전히 높은 커버리지의 테스트를 작성해야 안전한 코드라고 보장할 수 있다. 하지만 예전처럼 예상하지 못한 undefined가 나와서 문제가 발생한 부분이 아닌 다른 곳부터 추적해가야 할 일은 없어졌다.

 또 다른 문제는 babel에서 다르게 동작한다는 것이다. 파이어폭스나 크롬 등 모던 브라우저나 io.js는 ECMAScript 6를 지원하지만, 오래된 버전의 IE나 node.js 등에서는 아직 let을 지원하지 않는다. 그런 환경에서는 babel을 사용해야 하는데, babel에서는 중복된 선언이나, 선언 전에 사용하는 것을 에러로 처리하지 않는다.
 하지만 조만간 node.js에서도 let을 지원할 것이고, 오래된 버전의 IE도 없어지고 있으니 이런 문제는 곧 해결될 것이다.