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

2018-05-06

2018년 18번째 주

이 포스팅은 그냥 지난 한 주간 읽었던 것들을 정리하는 포스트입니다. 그냥 예전에 봤던 글 중 나중에 필요한데 뭐였는지 기억 안 나는 글들이 있어서 쓰기 시작했습니다.
 보통 하는 일과 관련된 글들이 올라오겠지만 딱히 정해둔 주제는 없고, 그때그때 관심 있었던 것을 읽었기 때문에 지난주에 쓰인 글일 수도 있고 몇 년 전에 쓰인 글일 수도 있습니다.


3개월 전까지 C++을 쓰다가 최근에 회사를 옮기면서 Rust를 쓰고 있다. 개인적으로 느끼기에 Rust가 C++에 비해 가지는 가장 큰 장점은 cargo라고 생각한다. Cargo 덕분에 C++에 비해서 의존성 관리를 매우 쉽게 할 수 있다. 물론 최근에 나온 언어들은 대부분 패키지 매니저를 가지고 있다. 하지만 그들은 대부분 C++보다 추상화된 메모리 관리를 가정하고 있기 때문에 C++의 대안이 되지는 못한다고 생각한다.

흔히들 말하는 Rust의 메모리 안전성은 딱히 큰 장점으로 느껴지지 않는다. Modern C++에서 제공하는 기능들을 잘 사용하면 C++에서도 메모리 이슈로 문제가 될 일은 많지 않다. 물론 C++을 쓸 때는 잘 써야 한다는 전제가 있어서 Rust를 쓸 때는 때 걱정을 덜 해도 된다는 것은 큰 장점이다. 하지만 Rust가 메모리 안전성은 보장해도 false alarm을 발생하는 일도 자주 있다. Non-lexical lifetime 같은 것이 구현되면서 false alarm을 줄이고 있지만, 아직은 종종 false alarm 때문에 실제로 안전한 코드를 보기 안 좋게 수정해야 하는 경우도 있기 때문에 어느 쪽이 더 좋은지는 취향의 문제라고 생각한다.

Date format by country

우리나라를 비롯한 중국 일본은 흔히 YYYY-MM-DD 형식을 쓴다. 인도, 동남아, 유럽, 남미, 러시아는 DD-MM-YYYY 형식을 주로 사용한다. 우리나라에서 사용하는 형식을 보통 big endian이라고 부르고, 유럽 등지에서 사용하는 형식을 little endian이라고 부른다. 두 형식의 날짜를 전부 처리해야 하기 때문에 약간 귀찮기는 하지만 여기까지는 그래도 괜찮다.

근데 미국은 MM-DD-YYYY라는 괴랄한 표기법을 사용한다. 거의 인치법 이상으로 제정신이 아닌 표기법 같다. 물론 그들에게도 이유는 있다. 보통 날짜를 말할 때 연도는 중요한 것이 아니다. 오늘이 며칠인지 물어보면 5월 6일이라고 말하지 2018년인지는 말하지 않는다. 중요하지 않은 정보라서 뒤에 적는다는 것이 그들이 말하는 이유다. 근데 그럴 거면 그냥 연도를 적지 말았어야 한다고 생각한다. 내가 연도를 말할지 말지 날짜를 말할 때까지 아무 생각 안 하다가 몇 월 며칠인지 말하고 연도를 말한다는 게 잘 상상이 안 된다. 사실 상상이 되고 말고를 떠나서 middle endian의 날짜 포맷까지 처리하고 나면 짜증이 난다.

참고로 ISO 8601은 우리나라처럼 big endian을 쓰도록 규정한다. 사실 컴퓨터에서 처리하기에는 big endian이 가장 좋다. 그래서 미국이나 유럽에서 개발된 프로그램도 제대로 된 프로그램은 대부분 big endian으로 날짜를 보여준다.

A Taxonomy of Expression Value Categories

C++ 스펙에서 value category가 초안에서 어떻게 바뀌었는지 보여주는 문서다. C++ 11 이전의 lvalue, rvalue에서 xvalue가 추가되면서 어떻게 변했는지 보여준다. 개인적으로 Bjarne Stroustrup이 쓴 "New" Value Terminology와 함께 C++의 value category를 이해하는 데 가장 도움이 되는 문서라고 생각한다.

IMHO를 In My Honest Opinion이라고 받아들이는 사람이 더 많다고 한다. 리뷰나 토론할 때 많이 쓰는 표현인데 앞으로는 약자 쓰는 것도 조심해서 써야겠다.

참고로 buzzfeed가 했다는 설문은 Help Us Solve This Debate About What "IMHO" Stands For로 보인다.

2017-07-12

[C++] glvalue와 prvalue

 지난번 글에서 lvalue와 rvalue의 특징을 설명하면서 예고했듯이 이번에는 glvalue와 prvalue의 특징에 관해서 설명하도록 하겠다.
 전에도 말했듯이 C++의 value category의 최하단에 있는 lvalue, xvalue, prvalue 중에서 lvalue와 xvalue가 glvalue에 포함된다. lvalue는 iM, xvalue는 im이고, prvalue는 Im이므로 glvalue와 prvalue를 나누는 기준은 i인지 아닌지. 즉, identity를 가지는지 여부이다. identity를 가진다는 것은 그 값이 expression을 넘어서까지 살아있다는 것이다. 그래서 identity를 가진다는 것은 그 값이 persist하다고 말하기도 한다.

temporary object

 prvalue는 persist 하지 않다. 이는 prvalue인 expression이 의미하는 object가 특정한 스토리지를 점유하지 않는다는 것이다. 만약 스토리지에 할당될 필요가 있으면 prvalue는 temporary object를 생성한다. 생성된 temporary object는 레퍼런스 변수에 할당되지 않으면, expression이 끝나고 소멸한다.

incomplete type

 prvalue는 구체화할 때 temporary object를 생성하므로, 컴파일 타임에 expression의 타입을 정확히 알아야 한다. 따라서 전방 선언만 돼있는 incomplete type인 prvalue는 있을 수 없다. 하지만 실행될 일 없는 decltype 안에 있는 expression의 경우 incomplete type인 prvalue가 있을 수 있다.

polymorphic

 incomplete type의 prvalue가 있을 수 없는 것과 비슷하게 prvalue는 polymorphic 할 수 없다. 다시 말해서 prvalue인 object의 dynamic type은 그 expression의 static type과 항상 같다. persist 하지 않다는 prvalue의 특성을 생각하면 이것도 매우 당연한 성질이다. C++에서 어떤 object를 polymorphic하게 사용하려면, derived type의 object를 만들어 놓고 base type의 레퍼런스로 접근해야 한다.  하지만 변수나 레퍼런스가 아닌 타입을 리턴하는 함수는 prvalue가 될 수 없으므로, prvalue는 polymorphic 할 수 없다는 것은 자연스럽다.

2017-06-30

[C++] lvalue와 rvalue

 지난번 글에서 C++의 value category에 관하여 설명하였다. 요약하면 다른 값으로 move될 수 있는지와 identity를 가지는지에 따라서 lvalue, xvalue, prvalue로 나뉘고, lvalue와 xvalue를 합쳐서 glvalue, xvalue와 prvalue를 합쳐서 rvalue라고 부른다는 것이다.
 앞에서도 말했듯이 C++의 value category를 나누는 기준은 move 될 수 있는지와 identity를 가지는지 여부이다. 이중 iM은 lvalue라고 부르고, im인 xvalue와 Im인 prvalue를 합쳐 rvalue라고 부른다. identity를 가지지도 않고, move 될 수도 없는 IM은 C++에 존재하지 않기 때문에 사실상 lvalue와 rvalue를 나누는 기준은 move 될 수 있는지 아닌지이다. 따라서 move 될 수 없으면 lvalue이고, move될 수 있으면 rvalue이다. 이번 글에서는 그래서 lvalue와 rvalue가 구분되는 특성을 설명할 것이고 다음 기회에 glvalue와 prvalue가 구분되는 특성을 설명하도록 하겠다.

overloaded function

 우선 lvalue와 rvalue의 가장 중요한 차이는 overload 된 함수가 있을 때 어떤 함수가 호출될지가 달라진다는 것이다.
위처럼 같은 타입의 lvalue reference를 받는 함수와 rvalue reference를 받는 함수가 overload돼있을 때, argument가 lvalue이면 lvalue reference를 받는 함수가 호출되고, rvalue이면 rvalue reference를 받는 함수가 호출된다.
따라서 위와 같이 사용했을 때, func(a)a가 lvalue이므로 0이 리턴되지만, std::move(a)는 xvalue이고 1prvalue이므로 func(std::move(a))func(1)1이 리턴된다.

universal reference

 rvalue reference(&&)처럼 생겼지만, 타입 추론을 해야 하는 경우. 즉, template parameter T가 있을 때 T&&라거나, auto&& 같은 경우 이 타입은 lvalue reference가 될 수도 있고, rvalue reference가 될 수도 있다. 그래서 이런 레퍼런스는 universal reference라고 불린다. 이 universal reference는 bind 되는 값이 lvalue이면 lvalue이면 lvalue reference가 되고, rvalue이면 rvalue reference가 된다. C++의 템플릿 타입 추론에 대해서 설명할 일이 있으면 더 자세히 설명하겠다. 우선은 universal reference는 bind 되는 값이 lvalue인지 rvalue인지에 따라서 어떤 값인지에 따라서 달라진다는 것만 알아두자.

& operator

 lvalue가 rvalue와 다른 중요한 특성 중 하나는 주소를 가지고 오는 & operator의 인자로 lvalue는 사용할 수 있지만, rvalue는 사용할 수 없다는 것이다. 언뜻 생각하면 identity를 가지는 xvalue의 주소를 가지고 오는 것도 가능할 것 같지만, C++11은 & operator의 인자로 올 수 있는 것은 lvalue만이라고 한정하고 있다.

assign operator

 또 다른 특징은 rvalue는 assign operator(=)의 왼쪽에 올 수 없다는 것이다. 조심해야 할 것은 rvalue가 assign operator의 왼쪽에 올 수 있다는 것이지, lvalue가 assign operator의 오른쪽에 올 수 있다는 말은 아니라는 것이다. C++에는 const modifier가 있으므로 모든 lvalue가 assign operator의 왼쪽에 올 수 있지는 않다. 따라서 왼쪽에 올 수 있는 것은 modifiable lvalue라고 따로 분류해서 부른다.


  • 2017-06-30 universal reference 부분을 추가했습니다.

2017-06-17

왜 c++은 복잡한 value category를 가지게 됐는가

 C++11이 나오기 전, C++의 value category는 lvalue와 rvalue만으로 이루어진 단순한 분류체계를 가지고 있었다. 하지만 C++11이 나오면서 xvalue, glvalue, prvalue가 추가되면서 복잡한 분류체계를 가지게 됐다. 이번 글에서는 C++11에서 변경된 value category에 대해서 알아보도록 하겠다.

until c++03

 C++11에서 새로 추가된 value들을 알기 전에 C++03 이전에도 있었던 lvalue와 rvalue가 뭔지부터 확실히 하고 넘어가야 한다. 흔히들 많이 실수하는 것이 lvalue는 assign operator(operator =)의 왼쪽에 올 수 있는 값이고, rvalue는 올 수 없는 값이라고 생각하는 것이다. C에서 lvalue라는 개념이 처음 나왔던 시절에는 assign operator를 기준으로 lvalue인지 아닌지를 구분하는 것이 맞았다. 하지만 C89에서 const 한정자가 추가되면서 더는 맞지 않다. const variable은 lvalue이지만 assign operator의 왼쪽에 올 수 없기 때문이다.
 따라서 C89에서는 lvalue를 left value가 아닌 locator value. 즉, 실제 값이 아닌 값이 있는 주소를 지칭하는 locator value라고 정의했다. 즉, 변수는 locator이기 때문에 lvalue이고, 1, 2 같은 숫자 리터럴이나 'a', 'b' 같은 문자 리터럴, 혹은 함수의 실행 결괏값 같은 경우 특정한 주소를 지칭하는 locator가 아니므로 lvalue가 아니다. 이 중에서 const 한정자가 붙지 않은 경우를 modifiable lvalue라고 따로 분류하여, modifiable lvalue만 assign operator의 왼쪽에 올 수 있다. 후에 나온 C++98도 C89의 lvalue 정의를 따랐기 때문에 C++98에서의 lvalue도 locator 라고 보면 된다. 다만 C89와 다르게 레퍼런스를 리턴하는 함수가 존재하기 때문에, 함수의 결과 타입이 레퍼런스일 경우, 함수의 실행 결과가 lvalue가 된다.

identity

 C++11에서는 기존의 lvalue가 가지는 특성을 identity를 가진다고 표현한다. 어떤 expression이 identity를 가진다고 하면 그 expression은 다른 expression이 표현하는 것이 같은 것(원문은 entity라고 하는데 이를 표현할 번역을 못 찾았다.)인지 비교할 수 있다는 것을 의미한다. 예를 들어 a라는 변수와 b라는 변수가 있으면 이 둘은 주소를 비교해서 같은 값을 가리키는지 아닌지 비교할 수 있으니 변수는 lvalue이다. 하지만 숫자 literal이나 boolean literal은 두 expression이 같은 entity인지 비교할 수 없기 때문에 rvalue이다. 또한 리턴 타입이 레퍼런스가 아닌 함수의 실행이나 타입 캐스팅같이 임시값으로 해당 expression이 넘어가면 사라지는 임시값들도 rvalue이다. 즉, lvalue의 경우 expression이 넘어서까지 entity가 존재하는 expression이기 때문에 identity를 가진다는 것을 persistence가 있다고 표현하기도 한다.

move

 C++11에서는  identity 말고 한 가지 특성이 더 추가됐다. C++11의 새 기능 중 가장 유명한 move가 추가됐기 때문이다. 모든 값이 move 될 수 있는 것은 아니기 때문에 이제 C++에서는 어떤 값이 다른 값으로 move 될 수 있는지 아닌지도 매우 중요한 특성이 됐다.
 이제부터 어떤 값이 move 될 수 있으면 m, move 될 수 없으면 M이라고 부르겠다. 또한, 앞에서 설명한 identity를 가지는 경우 이 값을 i, identity를 가지지 않는 경우 I라고 부른다. 이 identity를 가지는지와 move 될 수 있는지를 조합하면 iM, im, Im, IM 4가지 조합이 나온다. 하지만 이 중 IM. 즉, identity를 가지지도 않고, move 될 수도 없는 값은 있을 필요가 없으므로 우리는 iM, im, Im 3가지 조합만 살펴보면 된다.

iM - lvalue

 iM. 즉, identity를 가지지만, move 될 수도 없는 expression을 C++11에서는 lvalue라고 부른다. C++03까지의 lvalue 정의에 move 될 수 없다는 조건이 더 추가된 것이다. lvalue의 가장 큰 특징은 lvalue만이 & operator의 피연산자로 사용될 수 있다는 것이다, Im은 identity를 가지지 않기 때문에, iM은 move 돼 버릴 수 있으므로 & operator의 피연산자로 사용될 수 없다.

im - xvalue

 identity를 가지지만 move 될 수 있는 im의 경우는 eXpiring에서 따와 xvalue라고 부른다. xvalue는 이름 그대로 만료되어 가는 값이다. 따라서 expression이 끝나고 expression이 의미하던 주소로 접근했을 때 값이 존재할 수도 있고, 존재하지 않을 수도 있다. rvalue reference를 리턴하는 함수의 결괏값이나 rvalue rference로 타입 캐스팅 하는 expression이 xvalue에 해당한다. std::move도 rvalue reference를 리턴하는 함수이기 때문에 무언가를 move한 값은 xvalue에 속하고, lvalue를 move할 수 있게 만드는데 사용된다.

Im - prvalue

 마지막으로 identity를 가지지 않는 Im은 pure rvalue라는 의미에서 prvalue라고 부른다. 이는 C++03까지의 rvalue와 같다고 봐도 무방하다. prvalue는 C++에서 유일하게 identity를 가지지 않는다. non-reference를 리턴하는 함수의 실행결과나 non-reference로의 타입 캐스팅 혹은 숫자 boolean 리터럴들이 여기에 속한다.
 또한, identity를 가지지 않는다는 특성 때문에 prvalue는 다른 값들과 다르게 incomplete type일 수 없다. expression이 사용되는 시점에서 값이 존재해야 하기 때문이다.

m - rvalue

 Im이 rvalue가 아니라 pure rvalue인 이유는 rvalue가 따로 있기 때문이다. C++03 이전에는 identity를 가지지 않는 값을 rvalue라고 불렀지만, C++11에서는 m. 즉, move될 수 있는 값을 rvalue라고 부른다. 그래서 im인 xvalue와 Im인 prvalue 둘은 rvalue에 속하고, move될 수 없는 im, lvalue는 rvalue가 아니다.

i - glvalue

 move 될 수 있는지를 기준으로 lvalue와 rvalue를 나누듯이 identity를 가지는지 아닌지로 glvalue와 prvalue를 나눈다. 그래서 iM(lvalue)와 im(xvalue)는 glvalue로 분류된다. identity를 가진다는 것은 해당 expression이 끝나도 object는 남아있다는 것이고, 이 expression은 그저 object를 가리키는 무언가일 뿐이라는 것이다. 그래서 glvalue에 속하는 lvalue나 rvalue는 incomplete 한 타입일 수 있고, polymorphic 할 수도 있다.