프로퍼티
프로퍼티 (Properties)
D 언어에서 심볼(symbol), 타입, 표현식에 붙어 있는 프로퍼티(property) 를 함께 살펴볼게요. 프로퍼티는 . 뒤에 이름을 붙여 얻는 일종의 내장 정보로, 예를 들어 int.sizeof처럼 타입의 크기나 초깃값 같은 것들을 물어볼 수 있어요. 각 프로퍼티가 어떤 경우에 쓰이고, 어떤 값을 돌려주는지 하나씩 짚어가면서 보겠습니다.
본문
공통 프로퍼티 (Common Properties)
모든 심볼과 타입, 표현식에는 물어볼 수 있는 프로퍼티가 있어요. 그중 타입에 관계없이 공통으로 쓸 수 있는 두 가지를 먼저 볼게요.
| 프로퍼티 | 설명 |
|---|---|
.mangleof |
타입의 '맹글(mangled)' 표현을 나타내는 문자열 |
.stringof |
타입의 소스 표현을 나타내는 문자열 |
예시를 보면 금방 감이 와요.
| 표현식 | 값 |
|---|---|
int.mangleof |
문자열 "i"를 돌려줌 |
int.stringof |
문자열 "int"를 돌려줌 |
(1+2).stringof |
문자열 "1 + 2"를 돌려줌 |
타입 프로퍼티 (Type Properties)
모든 타입에도 프로퍼티가 있고, 모든 표현식에도 동일한 프로퍼티가 있어요. 표현식의 프로퍼티는 그 표현식이 가진 타입의 프로퍼티와 같다고 이해하면 됩니다.
| 프로퍼티 | 설명 |
|---|---|
.init |
초기화 값 (initializer) |
.sizeof |
크기 (바이트 단위) |
.alignof |
정렬 크기 (alignment size) |
예시입니다.
| 표현식 | 값 |
|---|---|
int.init |
0을 돌려줌 |
int.sizeof |
4를 돌려줌 |
(3).sizeof |
4를 돌려줌 (3은 int이기 때문) |
숫자 프로퍼티 (Numeric Properties)
숫자 타입에는 타입에 따라 몇 가지 프로퍼티가 더 있어요.
정수 타입(Integral Type)용 프로퍼티
| 프로퍼티 | 설명 |
|---|---|
.max |
최댓값 |
.min |
최솟값 |
예시입니다.
| 표현식 | 값 |
|---|---|
byte.min |
-128을 돌려줌 |
ubyte.max |
255를 돌려줌 |
부동소수점 타입(Floating Point Type)용 프로퍼티
| 프로퍼티 | 설명 |
|---|---|
.infinity |
무한대 값 |
.nan |
NaN - Not a Number 값 (다른 NaN 값들도 만들어낼 수 있음) |
.dig |
정밀도의 십진 자릿수 |
.epsilon |
값 1에 대한 가장 작은 증가분 |
.mant_dig |
가수(mantissa)의 비트 수 |
.max_10_exp |
10max_10_exp가 표현 가능한 최대 int 값 |
.max_exp |
2max_exp-1가 표현 가능한 최대 int 값 |
.min_10_exp |
10min_10_exp가 정규화된 값으로 표현 가능한 최소 int 값 |
.min_exp |
2min_exp-1가 정규화된 값으로 표현 가능한 최소 int 값 |
.max |
무한대가 아닌, 표현 가능한 가장 큰 값 |
.min_normal |
0이 아닌, 표현 가능한 가장 작은 정규화된 값 |
예시입니다.
| 표현식 | 값 |
|---|---|
float.nan |
부동소수점 NaN 값을 돌려줌 |
(2.5F).nan |
부동소수점 NaN 값을 돌려줌 |
복소 부동소수점 타입(Complex Floating Point Type)용 추가 프로퍼티
| 프로퍼티 | 설명 |
|---|---|
.re |
실수부 (real part) |
.im |
허수부 (imaginary part) |
파생 타입 프로퍼티 (Derived Type Properties)
다음을 참고하세요.
- 배열 프로퍼티 (Array Properties)
- 연관 배열 프로퍼티 (Associative Array Properties)
- 벡터 프로퍼티 (Vector Properties)
사용자 정의 타입 프로퍼티 (User-Defined Type Properties)
다음을 참고하세요.
- 클래스 프로퍼티 (Class Properties)
- 열거형 프로퍼티 (Enum Properties)
- 구조체 프로퍼티 (Struct Properties)
.init 프로퍼티
.init은 기본 초기화 값(default initializer)이 되는 상수 표현식을 만들어내요. 타입에 적용하면 그 타입의 기본 초기화 값이 되고, 변수나 필드에 적용하면 그 변수·필드의 타입에 대한 기본 초기화 값이 됩니다. 타입 종류별 기본값은 아래와 같아요.
| 타입 | .init 값 |
|---|---|
char |
'\xff' |
dchar |
'\xffff' |
wchar |
'\xffff' |
| 열거형 (Enum) | 첫 번째 멤버 값 |
| 정수 (Integers) | 0 |
| 부동소수점 (Floating Point) | NaN |
| 참조 타입 (Reference Types) | null |
| 구조체 (Structs) | 각 필드의 기본값 |
| 공용체 (Unions) | 첫 번째 멤버 값 |
근거(Rationale): 가능한 한 타입의 기본값은 '유효하지 않은 값(invalid value)'이 되도록 한다는 것입니다. 그래서 문자열 타입의 기본값이
'\xff'같은 다소 특이해 보이는 값이 되는 거예요.
int a;
int b = 1;
static assert(int.init == 0);
static assert(a.init == 0);
static assert(b.init == 0);
struct Foo
{
int a;
int b = 7;
}
static assert(Foo.init.a == 0);
static assert(Foo.init.b == 7);
.init 값이 타입 기준이라, b는 선언에서 1로 초기화됐는데도 b.init == 0이라는 게 이 예시의 핵심이에요. 프로퍼티가 변수의 "현재 값"이 아니라 그 변수의 타입의 기본값을 돌려주는 점을 확인하면 좋겠죠.
.init vs 기본 초기화 (Default Initialization)
.init은 정적으로 초기화된 객체(static initializer) 를 만들어낸다는 점을 눈여겨볼게요. 값 타입의 경우 이것이 기본 초기화(default initialization)와 다를 수 있습니다.
예를 들어 중첩 구조체(nested struct) S의 경우, S.init 안의 컨텍스트 포인터(context pointer)는 null이에요.
void main()
{
int x;
struct S
{
void foo() { x = 1; } // access x in enclosing scope via context pointer
}
S s1; // OK, s1 correctly initializes its context pointer
S s2 = S(); // OK, same as s1
s1.foo(); // OK
S s3 = S.init; // Beware, the context pointer in s3 is null
s3.foo(); // Access violation
}
여기서 s1이나 s2처럼 선언하면 컨텍스트 포인터가 제대로 초기화돼서 s1.foo()가 잘 동작해요. 그런데 S.init으로 만들면 컨텍스트 포인터가 null이라서 s3.foo()가 접근 위반(Access violation)을 일으켜요. 코드의 주석에 "Beware"라고 경고가 달려 있는 게 바로 이 때문입니다.
또, 구조체 S에 @disable this();가 있으면 T.init이 논리적으로 올바르지 않은 객체를 돌려줄 수도 있어요.
struct S
{
int x;
@disable this();
this(int n) { x = n; }
invariant { assert(x > 0); }
void check() {}
}
void main()
{
//S s1; // Error: variable s1 initializer required for type S
//S s2 = S(); // Error: constructor S.this is not callable
// because it is annotated with @disable
S s3 = S.init; // Bad. s3.x == 0, and it violates the invariant of S
s3.check(); // Assertion failure
}
일반적인 생성 경로(@disable로 막혀 있으니)는 아예 컴파일 오류로 막혀 있어요. 그런데 .init은 생성자를 거치지 않으니 컴파일은 되지만, s3.x == 0이 되어 invariant { assert(x > 0); }를 위반해버립니다.
마찬가지로, 구조체의 정적 배열(static array)도 .init과 기본 초기화에서 서로 다른 동작을 보일 수 있어요.
.stringof 프로퍼티
.stringof는 자기 앞(prefix)의 소스 표현을 나타내는 상수 문자열을 만들어내요. 타입에 적용하면 그 타입의 문자열이 되고, 표현식에 적용하면 그 표현식의 소스 표현이 됩니다. 이때 표현식은 실제로 평가(evaluate)되지 않아요.
module test;
import std.stdio;
struct Dog { }
enum Color { Red }
int i = 4;
void main()
{
writeln((1+2).stringof); // "1 + 2"
writeln(test.stringof); // "module test"
writeln(Dog.stringof); // "Dog"
writeln(int.stringof); // "int"
writeln((int*[5][]).stringof); // "int*[5][]"
writeln(Color.Red.stringof); // "Red"
writeln((5).stringof); // "5"
writeln((++i).stringof); // "i += 1"
assert(i == 4); // `++i` was not evaluated
}
마지막 부분이 특히 재미있어요. (++i).stringof가 문자열 "i += 1"을 돌려주긴 하지만, 그 다음 assert(i == 4)가 성공해요. 즉 ++i가 평가되지 않았다는 뜻이에요. 문자열만 뽑아낼 뿐 부수 효과(side effect)는 전혀 발생하지 않는다는 점이 .stringof의 큰 특징입니다.
구현 정의(Implementation Defined): 타입이나 표현식에 대한 문자열 표현은 구현마다 달라질 수 있습니다.
모범 사례(Best Practices):
.stringof를 코드 생성(code generation) 용도로 사용하지 마세요. 대신identifier트레잇(trait)이나std.traits.fullyQualifiedName같은 Phobos 헬퍼 함수를 사용하는 게 좋습니다.
.sizeof 프로퍼티
e.sizeof는 표현식 e의 크기(바이트 단위) 를 돌려줘요.
멤버의 크기를 구할 때는 this 객체가 필요하지 않아요.
struct S
{
int a;
}
static assert(S.a.sizeof == 4);
static assert(Object.sizeof == (void*).sizeof);
여기서 S.a.sizeof가 4라는 걸 컴파일 타임에 확인할 수 있어요. 인스턴스가 없어도 필드의 크기를 직접 물어볼 수 있는 거죠.
.sizeof를 클래스 객체에 적용하면 클래스 참조(reference)의 크기를 돌려주지, 클래스 인스턴스(instantiation)의 크기를 돌려주는 게 아니라는 점에 주의해야 해요.
참고: __traits(classInstanceSize)를 함께 보세요.
.alignof 프로퍼티
.alignof는 표현식이나 타입의 정렬된 크기(aligned size) 를 돌려줘요. 예를 들어 정렬 크기가 1이면 바이트 경계에, 4면 32비트 경계에 정렬되어 있다는 뜻입니다.
align 속성(attribute)의 예시를 함께 보세요.
구현 정의(Implementation Defined): 실제 정렬 크기 값은 구현마다 달라집니다.
모범 사례(Best Practices): 외부에서 정해진 레이아웃과 맞춰야 하는 객체를 배치할 때는 특히 주의하세요. 데이터 정렬이 어긋나면 특히 교묘한 버그를 일으키기 쉬워요. 올바른지 확실히 하기 위해
assert를 하나 넣어두는 게 유용할 때가 많습니다.
.mangleof 프로퍼티
맹글링(Mangling)은 심볼이 생성된 객체 파일(object file)에서 텍스트 형태로 어떻게 표현되는지를 말해요. .mangleof는 적용 대상인 타입이나 심볼의 표현을 나타내는 문자열 리터럴을 돌려줍니다. D 링키지(linkage)를 가진 타입과 심볼의 맹글링은 Name Mangling에 정의되어 있어요.
구현 정의(Implementation Defined): 심볼에 앞줄 밑줄(leading underscore)이 붙는지 여부, 그리고 D가 아닌 링키지를 가진 타입·심볼의 맹글링 방식은 구현마다 다릅니다. C/C++ 링키지의 경우 보통 해당 C/C++ 컴파일러가 하는 것과 일치하게 됩니다.
.classinfo 프로퍼티
.classinfo는 클래스 객체의 동적 타입(dynamic type) 에 대한 정보를 제공해요. object.TypeInfo_Class 타입의 참조를 돌려줍니다.
.classinfo를 인터페이스에 적용하면, 그 인터페이스가 인스턴스인 클래스가 아니라 인터페이스 자체의 정보를 돌려줘요.
.tupleof 프로퍼티
.tupleof는 구조체나 클래스 안의 모든 비정적(non-static) 필드들의 심볼 시퀀스를 제공해요. 튜플 안의 필드 순서는 필드가 선언된 순서와 일치합니다.
클래스의 경우, 숨겨진 필드와 기저 클래스(base class)의 필드는 제외돼요.
가장 흔한 용도는 foreach 루프로 필드들을 순회하는 것입니다.
struct Vector3
{
int x, y, z;
}
void test()
{
Vector3 v = Vector3(5, 10, 15);
float sum = 0;
foreach(num; v.tupleof)
{
sum+= num;
}
writeln(sum); //30
}
v.tupleof가 (x, y, z) 시퀀스로 풀리면서 각 필드 값 5, 10, 15를 순회하고, 그 합인 30이 출력되는 걸 확인할 수 있어요.
사용자 정의 프로퍼티 (User-Defined Properties)
사용자 정의 프로퍼티는 프로퍼티 함수(Property Functions) 를 이용해 만들 수 있어요.
더 알아보기 (Learn more)
- 배열 프로퍼티 (Array Properties)
- 연관 배열 프로퍼티 (Associative Array Properties)
- 벡터 프로퍼티 (Vector Properties)
- 클래스 프로퍼티 (Class Properties)
- 열거형 프로퍼티 (Enum Properties)
- 구조체 프로퍼티 (Struct Properties)
- 프로퍼티 함수 (Property Functions)
__traits(classInstanceSize),identifier트레잇