애트리뷰트
애트리뷰트 (Attributes)
D 언어에서 선언 하나에도, 여러 선언에도 동시에 붙일 수 있는 '애트리뷰트(attribute)'에 대해 다루는 챕터예요. 애트리뷰트를 잘 알면 코드에 붙은 const, pure, @safe 같은 표식들이 실제로 어떤 의미인지, 언제 어떤 효과가 있는지 또렷하게 보여요. 이 장에서는 문법부터 시작해 가시성(visibility), 가변성(mutability), 공유 저장소, 함수 애트리뷰트, 그리고 사용자 정의 애트리뷰트(UDA)까지 하나씩 살펴볼게요.
본문
문법 (Grammar)
AttributeSpecifier:
Attribute :
Attribute DeclarationBlock
Attribute:
AlignAttribute
AtAttribute
DeprecatedAttribute
FunctionAttributeKwd
LinkageAttribute
Pragma
VisibilityAttribute
abstract
auto
const
final
__gshared
extern
immutable
inout
override
ref
__rvalue
scope
shared
static
synchronized
FunctionAttributeKwd:
nothrow
pure
AtAttribute:
@ disable
@ __future
@ __ctfe
@ nogc
@ live
Property
@ safe
@ system
@ trusted
UserDefinedAttribute
Property:
@ property
DeclarationBlock:
DeclDef
{ DeclDefsopt }
애트리뷰트는 하나 이상의 선언을 수정하는 방법이에요. 일반적인 형태는 이렇게 세 가지예요.
attribute declaration; // affects the declaration
attribute: // affects all declarations until the end of
// the current scope
declaration;
declaration;
...
attribute // affects all declarations in the block
{
declaration;
declaration;
...
}
함수 애트리뷰트인 @nogc, nothrow, pure는 집합체(aggregate) 선언 안으로는 전파되지 않아요.
const pure @safe
{
int i; // function attributes ignored for non-functions
void f(); // const attribute ignored for free function
struct S
{
int i;
void f(); // pure ignored inside struct
}
}
static assert(is(typeof(i) == const int));
static assert(is(typeof(&f) == void function() pure @safe));
static assert(is(typeof(S.i) == const int));
static assert(is(typeof(&S().f) == void delegate() const @safe));
컴파일러가 인식하는 다른 애트리뷰트들에 대해서는 core.attribute를 함께 보면 좋아요.
링키지 애트리뷰트 (Linkage Attribute)
LinkageAttribute:
extern ( LinkageType )
extern ( C ++ , )
extern ( C ++ , QualifiedIdentifier )
extern ( C ++ , NamespaceList )
extern ( C ++ , class )
extern ( C ++ , struct )
LinkageType:
C
C ++
D
Windows
System
Objective - C
NamespaceList:
ConditionalExpression
ConditionalExpression ,
ConditionalExpression , NamespaceList
D는 C 함수와 운영체제 API 함수를 쉽게 호출할 수 있는 방법을 제공해요. 둘 다 호환성이 핵심이라서죠. LinkageType은 대소문자를 구분하며, 키워드가 아니라서 구현체가 필요하면 확장할 수 있게 되어 있어요. C와 D는 반드시 제공해야 하고, 나머지는 구현체가 합리적이라고 판단하는 대로 채워 넣으면 돼요. C++은 C++과 제한적인 호환성을 제공하는데, 자세한 내용은 Interfacing to C++ 문서를 참고하세요. Objective-C는 Objective-C와의 호환성을 제공하고, Interfacing to Objective-C 문서에서 더 자세히 볼 수 있어요. System은 Windows 플랫폼에서는 Windows와 같고, 그 외 플랫폼에서는 C와 같아요.
구현 참고 (Implementation Note): Win32 플랫폼에서는
Windows가 존재해야 해요.
C 함수 호출 규약은 이렇게 지정해요.
extern (C):
int foo(); // call foo() with C conventions
D 규약은 이렇게요.
extern (D):
Windows API 규약은 이렇게요.
extern (Windows):
void *VirtualAlloc(
void *lpAddress,
uint dwSize,
uint flAllocationType,
uint flProtect
);
Windows 규약은 C 규약과 다른데, 그 차이는 Win32 플랫폼에서만 나타나고 그곳에서는 stdcall 규약과 같아요.
참고로, extern 키워드 하나만 쓰면 이건 저장 클래스(storage class)로 쓰인다는 점을 기억해 두세요.
C++ 네임스페이스 (C++ Namespaces)
extern (C++, QualifiedIdentifier) 형태의 링키지는 C++ 네임스페이스 안에 놓이는 C++ 선언을 만들어요. QualifiedIdentifier가 그 네임스페이스를 지정해 줘요.
extern (C++, N) { void foo(); }
위 코드는 이런 C++ 선언을 가리켜요.
namespace N { void foo(); }
그리고 한정자(qualification)를 붙이거나 붙이지 않고 모두 참조할 수 있어요.
foo();
N.foo();
네임스페이스는 새로운 이름 있는 스코프를 만들고, 그 스코프는 바깥 스코프로 가져와져요(import).
extern (C++, N) { void foo(); void bar(); }
extern (C++, M) { void foo(); }
void main()
{
bar(); // ok
//foo(); // error - N.foo() or M.foo() ?
M.foo(); // ok
}
QualifiedIdentifier에서 점으로 구분된 여러 식별자는 중첩된 네임스페이스를 만들어요.
extern (C++, N.M) { extern (C++) { extern (C++, R) { void foo(); } } }
N.M.R.foo();
위 코드는 이런 C++ 선언을 가리켜요.
namespace N { namespace M { namespace R { void foo(); } } }
align 애트리뷰트 (align Attribute)
AlignAttribute:
align ( default )
align
align ( AssignExpression )
align은 다음 대상들의 정렬(alignment)을 지정해요.
- 변수 (variables)
- 구조체 필드 (struct fields)
- 공용체 필드 (union fields)
- 클래스 필드 (class fields)
- 구조체·공용체·클래스 타입 (struct, union, and class types)
align(default)는 기본값으로 (다시) 설정하는데, 이 기본값은 짝을 이루는 C 컴파일러의 기본 멤버 정렬과 일치해요. align만 단독으로 쓰면 align(default)와 같아요.
struct S
{
align(default): // same as `align:`
byte a; // placed at offset 0
int b; // placed at offset 4
long c; // placed at offset 8
}
static assert(S.alignof == 8);
static assert(S.c.offsetof == 8);
static assert(S.sizeof == 16);
AssignExpression이 정렬을 지정하는데, 비기본 정렬을 쓸 때 짝을 이루는 C 컴파일러의 동작과 일치해요. 이 값은 0이 아닌 2의 거듭제곱이어야 해요.
값이 1이면 정렬을 전혀 하지 않는다는 뜻이에요. 필드들이 그냥 빈틈없이 붙어요.
struct S
{
align (1):
byte a; // placed at offset 0
int b; // placed at offset 1
long c; // placed at offset 5
}
static assert(S.alignof == 1);
static assert(S.c.offsetof == 5);
static assert(S.sizeof == 13);
집합체의 자연 정렬(natural alignment)은 그 필드들의 정렬 중 최댓값이에요. 집합체 바깥에서 정렬을 지정하면 이 자연 정렬을 덮어쓸 수 있어요.
align (2) struct S
{
align (1):
byte a; // placed at offset 0
int b; // placed at offset 1
long c; // placed at offset 5
}
static assert(S.alignof == 2);
static assert(S.c.offsetof == 5);
static assert(S.sizeof == 14);
필드의 정렬을 지정하면 필드 크기와 관계없이 그 2의 거듭제곱 경계에 맞춰 정렬돼요.
struct S
{
byte a; // placed at offset 0
align (4) byte b; // placed at offset 4
align (16) short c; // placed at offset 16
}
static assert(S.alignof == 16);
static assert(S.c.offsetof == 16);
static assert(S.sizeof == 32);
AlignAttribute는 함수 스코프나 익명이 아닌 구조체·공용체·클래스에 들어가면 기본값으로 재설정되고, 그 스코프를 빠져나가면 복원돼요. 그리고 베이스 클래스로부터는 상속되지 않아요.
구조체 레이아웃에 대해서는 Struct Layout을 참고하세요.
GC 호환성 (GC Compatibility)
NewExpression으로 할당된 참조나 포인터를 size_t의 배수가 아닌 경계에 정렬하지 마세요. 가비지 컬렉터는 GC에 할당된 객체를 가리키는 포인터와 참조가 size_t 바이트 경계에 놓일 거라고 가정하기 때문이에요.
struct S
{
align(1):
byte b;
int* p;
}
static assert(S.p.offsetof == 1);
@safe void main()
{
S s;
s.p = new int; // error: can't modify misaligned pointer in @safe code
}
deprecated 애트리뷰트 (deprecated Attribute)
DeprecatedAttribute:
deprecated
deprecated ( AssignExpression )
라이브러리에서 어떤 기능을 없애고 싶지만, 하위 호환성을 위해 남겨 둬야 하는 경우가 자주 있어요. 그런 선언에 deprecated를 붙이면, 어떤 코드가 그 선언을 참조할 때 컴파일러가 오류를 내도록 지시할 수 있어요.
deprecated
{
void oldFoo();
}
oldFoo(); // Deprecated: function test.oldFoo is deprecated
선택적으로 문자열 리터럴이나 매니페스트 상수로 폐기 메시지에 추가 정보를 담을 수도 있어요.
deprecated("Don't use bar") void oldBar();
oldBar(); // Deprecated: function test.oldBar is deprecated - Don't use bar
CTFE 가능한 함수를 호출하거나 매니페스트 상수를 쓰는 것도 가능해요.
import std.format;
enum message = format("%s and all its members are obsolete", Foobar.stringof);
deprecated(message) class Foobar {}
deprecated(format("%s is also obsolete", "This class")) class BarFoo {}
void main()
{
auto fb = new Foobar(); // Deprecated: class test.Foobar is deprecated - Foobar
// and all its members are obsolete
auto bf = new BarFoo(); // Deprecated: class test.BarFoo is deprecated - This
// class is also obsolete
}
구현 참고 (Implementation Note): 컴파일러는
deprecated를 무시할지, 경고를 낼지, 아니면 컴파일 중 오류를 낼지를 지정하는 스위치를 가져야 해요.
@__future 애트리뷰트 (@__future attribute)
@__future 애트리뷰트는 다른 모듈에서 이름 충돌(name clash)을 일으킬 수 있는 새로운 심볼을 표시할 때 써요. 같은 이름의 심볼을 쓰는 다른 모듈은 예전처럼 계속 컴파일되지만, 폐기(deprecation) 메시지를 보여줘요. 그 메시지는 새 심볼이 앞으로 기존 코드를 깨뜨릴 것이라는 뜻이고, 다른 모듈이 그에 맞춰 갱신되어야 한다는 걸 알려줘요.
자세한 내용은 DIP1007을 참고하세요.
@__ctfe 애트리뷰트 (@__ctfe attribute)
@__ctfe 애트리뷰트는 함수가 CTFE에서만 호출되도록 보장해요. 객체 코드 생성(object code generation)을 끄는 거죠. 이 애트리뷰트는 명령줄 플래그에서 비롯된 코드 생성 제한을 우회해요. @__ctfe 함수는 런타임 문맥에서 호출할 수 없고, 런타임에서 그 주소를 취하는 것도 오류예요.
int add(int a) @__ctfe => a + 2;
void main()
{
static int w = add(1); // OK
int x = add(1); // Error: function marked with @__ctfe cannot be called at runtime
auto fp = &add; // Error: cannot take address of function marked with @__ctfe
}
가시성 애트리뷰트 (Visibility Attributes)
VisibilityAttribute:
export
package
package ( QualifiedIdentifier )
private
protected
public
가시성(visibility)은 private, package, protected, public, export 중 하나인 애트리뷰트예요. DIP22 이전의 문서에서는 이들을 보호 애트리뷰트(protection attributes)라고 부르기도 했어요.
가시성은 심볼 이름 찾기(symbol name lookup)에 참여해요.
export 애트리뷰트 (export Attribute)
export는 심볼을 실행 파일·공유 라이브러리·DLL 밖에서 접근할 수 있게 만든다는 뜻이에요. 그 심볼은 정의된 곳에서 '내보내지고'(exported), 다른 실행 파일·공유 라이브러리·DLL에 의해 '가져와진다'(imported)고 표현해요.
심볼의 정의(definition)에 export를 붙이면 내보내는 거고, 선언(declaration)에 붙이면 가져오는 거예요. 변수는 extern이 붙지 않는 한 정의예요.
export int x = 3; // definition, exporting `x`
export int y; // definition, exporting `y`
export extern int z; // declaration, importing `z`
export __gshared h = 3; // definition, exporting `h`
export __gshared i; // definition, exporting `i`
export extern __gshared int j; // declaration, importing `j`
본문(body)이 있는 함수는 정의이고, 본문이 없는 함수는 선언이에요.
export void f() { } // definition, exporting `f`
export void g(); // declaration, importing `g`
Windows 용어로 말하면, dllexport는 DLL에서 심볼을 내보내는 것이고, dllimport는 DLL이나 실행 파일이 다른 DLL에서 심볼을 가져오는 것이에요.
package 애트리뷰트 (package Attribute)
package는 private을 확장해서, 같은 패키지 안에 있는 다른 모듈의 코드에서도 패키지 멤버에 접근할 수 있게 해줘요. 식별자를 주지 않으면 가장 안쪽 패키지에만 적용되고, 모듈이 패키지 안에 중첩되어 있지 않으면 private으로 기본 설정돼요.
package는 점으로 구분된 식별자 목록 형태의 선택적 매개변수를 가질 수 있는데, 이는 한정된 패키지 이름으로 해석돼요. 그 패키지는 모듈의 부모 패키지이거나 그 조상 중 하나여야 해요. 이 매개변수가 있으면, 심볼은 지정된 패키지와 그 모든 하위 패키지에서 보이게 돼요.
private 애트리뷰트 (private Attribute)
private 가시성을 가진 심볼은 같은 모듈 안에서만 접근할 수 있어요. private 멤버 함수는 암묵적으로 final이고, 오버라이드할 수 없어요.
protected 애트리뷰트 (protected Attribute)
protected는 클래스 안에서만 적용되고(믹스인될 수 있으므로 템플릿에도 적용돼요), 심볼을 같은 모듈의 멤버나 파생 클래스만 볼 수 있게 만들어요. 파생 클래스의 멤버 함수를 통해 protected 인스턴스 멤버에 접근할 때는, 그 멤버는 this와 같은 타입으로 암묵적으로 캐스팅될 수 있는 객체 인스턴스에 대해서만 접근할 수 있어요. protected 모듈 멤버는 허용되지 않아요.
public 애트리뷰트 (public Attribute)
public은 실행 파일 안의 어떤 코드든 그 멤버를 볼 수 있게 해줘요. 기본 가시성 애트리뷰트예요.
가변성 애트리뷰트 (Mutability Attributes)
const 애트리뷰트 (const Attribute)
const 타입 한정자(type qualifier)는 선언된 심볼의 타입을 T에서 const(T)로 바꿔요. 여기서 T는 const가 없을 때 그 심볼에 대해 지정(또는 추론)되는 타입이에요.
const int foo = 7;
static assert(is(typeof(foo) == const(int)));
const double bar = foo + 6;
static assert(is(typeof(bar) == const(double)));
class C
{
const void foo();
const
{
void bar();
}
void baz() const;
}
pragma(msg, typeof(C.foo)); // const void()
pragma(msg, typeof(C.bar)); // const void()
pragma(msg, typeof(C.baz)); // const void()
static assert(is(typeof(C.foo) == typeof(C.bar)) &&
is(typeof(C.bar) == typeof(C.baz)));
한정된 타입을 반환하는 메서드에 대해서는 Methods Returning a Qualified Type을 참고하세요.
immutable 애트리뷰트 (immutable Attribute)
immutable 애트리뷰트는 const가 하는 것과 같은 방식으로 타입을 T에서 immutable(T)로 바꿔요. 다음을 참고하면 좋아요.
- immutable 저장 클래스 (immutable storage class)
- immutable 타입 한정자 (immutable type qualifier)
inout 애트리뷰트 (inout Attribute)
inout 애트리뷰트는 const가 하는 것과 같은 방식으로 타입을 T에서 inout(T)로 바꿔요.
공유 저장소 애트리뷰트 (Shared Storage Attributes)
shared 애트리뷰트 (shared Attribute)
shared를 참고하세요.
__gshared 애트리뷰트 (__gshared Attribute)
기본적으로 immutable이 아닌 전역 선언은 스레드 로컬 저장소(thread local storage)에 놓여요. 전역 변수에 __gshared 애트리뷰트를 붙이면, 그 값이 모든 스레드에 걸쳐 공유돼요.
int foo; // Each thread has its own exclusive copy of foo.
__gshared int bar; // bar is shared by all threads.
__gshared는 멤버 변수와 지역 변수에도 적용할 수 있어요. 이 경우 __gshared는 static과 동등한데, 변수가 스레드 로컬이 아니라 모든 스레드에 공유된다는 점만 달라요.
class Foo
{
__gshared int bar;
}
int foo()
{
__gshared int bar = 0;
return bar++; // Not thread safe.
}
경고 (Warning):
shared애트리뷰트와 달리__gshared는 데이터 레이스(data races)나 기타 다중 스레드 동기화 문제에 대한 안전장치를 전혀 제공하지 않아요.__gshared로 표시된 변수 접근이 제대로 동기화되도록 보장하는 것은 프로그래머의 책임이에요.
__gshared는 @safe 코드에서는 허용되지 않아요.
synchronized 애트리뷰트 (synchronized Attribute)
Synchronized Methods를 참고하세요.
@disable 애트리뷰트 (@disable Attribute)
@disable 애트리뷰트로 비활성화할 수 있는 선언은 다음과 같아요.
- 함수, 생성자,
NewDeclaration - 변수, 매니페스트 상수,
EnumMember
비활성화된 선언을 사용하면 컴파일 타임 오류가 나요.
@disable int x;
@disable void foo();
void foo(int);
void main()
{
x++; // error, x is disabled
foo(); // error, foo is disabled
foo(5); // OK, not disabled
}
사용자 정의 타입 자체는 비활성화할 수 없지만, 생성자는 비활성화할 수 있어요. 특히:
- 구조체 안의
@disable this();는 기본 초기화를 허용하지 않게 해요. - 구조체 복사 생성자(copy constructor)를 비활성화하면 그 구조체를 복사할 수 없게 돼요.
- 구조체 이동 생성자(move constructor)를 비활성화하면 그 구조체를 이동할 수 없게 돼요.
@safe, @trusted, @system 애트리뷰트 (@safe, @trusted, and @system Attributes)
Function Safety를 참고하세요.
시스템 변수 (System Variables)
@system으로 표시된 변수는 @safe 코드에서 접근할 수 없어요.
@system int* p;
struct S
{
@system int i;
}
void main() @safe
{
int x = *p; // error with `-preview=systemVariables`, deprecation otherwise
S s;
s.i = 0; // ditto
}
함수 애트리뷰트 (Function Attributes)
@nogc 애트리뷰트 (@nogc Attribute)
No-GC Functions를 참고하세요.
@property 애트리뷰트 (@property Attribute)
Property Functions를 참고하세요.
nothrow 애트리뷰트 (nothrow Attribute)
Nothrow Functions를 참고하세요.
pure 애트리뷰트 (pure Attribute)
Pure Functions를 참고하세요.
ref 애트리뷰트 (ref Attribute)
ref Storage Class를 참고하세요.
return 애트리뷰트 (return Attribute)
__rvalue 애트리뷰트 (__rvalue Attribute)
static 애트리뷰트 (static Attribute)
static 애트리뷰트는 타입, 함수, 변수에 적용돼요. 다른 선언에 적용하면 무시돼요.
집합체 타입 안에서 static 선언은 객체의 특정 인스턴스가 아니라 객체의 타입에 적용돼요. 다시 말해, this 참조가 없다는 뜻이에요.
class Foo
{
static int x;
static int bar() { return x; }
int foobar() { return 7; }
}
Foo.x = 6; // no instance needed
assert(Foo.bar() == 6);
//Foo.foobar(); // error, no instance of Foo
Foo f = new Foo;
assert(f.bar() == 6);
assert(f.foobar() == 7);
static 메서드는 절대 가상(virtual)이 아니에요.
static 데이터는 객체당 하나가 아니라 스레드당 하나씩 존재해요.
static 중첩 함수나 타입은 부모 스코프의 변수에 접근할 수 없어요.
함수 안에서 static 지역 변수는 함수가 반환된 뒤에도 지속돼요.
static은 C에서처럼 '파일에 국한된다'는 추가 의미를 갖지 않아요. 그 용도는 D에서는 private 애트리뷰트를 써서 달성해요. 예를 들어:
module foo;
int x = 3; // x is global
private int y = 4; // y is local to module foo
auto 애트리뷰트 (auto Attribute)
auto 애트리뷰트는 다른 애트리뷰트가 없고 타입 추론이 필요할 때 써요.
auto i = 6.8; // declare i as a double
함수에 대해 auto 애트리뷰트는 반환 타입 추론을 의미해요. Auto Functions를 참고하세요.
scope 애트리뷰트 (scope Attribute)
scope 애트리뷰트는 변수의 포인터 값이 그 변수가 선언된 스코프를 벗어나 이스케이프(escape)하지 않는다는 뜻을 나타내요.
변수의 타입에 어떤 간접 참조(indirection)도 없으면 scope 애트리뷰트는 무시돼요.
전역 변수에 적용하면 scope도 무시돼요. 포인터가 이스케이프할 수 있는, 전역 스코프보다 더 큰 스코프가 없기 때문이에요.
scope int* x; // scope ignored, global variable
void main()
{
// scope static int* x; // cannot be both scope and static
scope float y; // scope ignored, no indirections
scope int[2] z; // scope ignored, static array is value type
scope int[] w; // scope dynamic array
}
간접 참조가 있는 타입의 지역 변수에 적용하면, 그 값은 수명(lifetime)이 더 긴 변수에 할당될 수 없어요.
- 그 변수가 선언된 Scope Statement 바깥의 변수
- 지역 변수는 선언된 순서의 역순으로 파괴되므로, scope 변수보다 먼저 선언된 변수
__gshared또는static변수
더 긴 수명의 변수에 암묵적으로 할당하는 다른 연산들도 허용되지 않아요.
- 함수에서 scope 변수 반환하기
- 함수를 호출할 때 scope 변수를 non-scope 매개변수에 할당하기
- scope 변수를 배열 리터럴에 넣기
scope 애트리뷰트는 타입의 일부가 아니라 변수 선언의 일부이고, 간접 참조의 첫 단계에만 적용돼요. 예를 들어, scope 포인터들의 동적 배열로 변수를 선언하는 것은 불가능해요. scope는 배열 자체의 .ptr에만 적용되고 그 요소에는 적용되지 않기 때문이에요. scope는 다양한 타입에 이렇게 영향을 줘요.
| 지역 변수의 타입 (Type of local variable) | scope가 적용되는 대상 (What scope applies to) |
|---|---|
| 모든 기본 데이터 타입 (Any Basic Data Type) | 없음 (nothing) |
포인터 T* (Pointer T*) |
포인터 값 (the pointer value) |
동적 배열 T[] (Dynamic Array T[]) |
요소로 가는 .ptr (the .ptr to the elements) |
정적 배열 T[n] (Static Array T[n]) |
각 요소 T (each element T) |
연관 배열 K[V] (Associative Array K[V]) |
구현 정의 구조체로 가는 포인터 (the pointer to the implementation defined structure) |
| struct 또는 union | 각 멤버 변수 (each of its member variables) |
| 함수 포인터 | 포인터 값 (the pointer value) |
| 델리게이트 (delegate) | .funcptr와 .ptr(클로저 문맥) 포인터 값 모두 (both the .funcptr and .ptr (closure context) pointer values) |
| class 또는 interface | 클래스 참조 (the class reference) |
| enum | 베이스 타입 (the base type) |
struct S
{
string str; // note: string = immutable(char)[]
string* strPtr;
}
string escape(scope S s, scope S* sPtr, scope string[2] sarray, scope string[] darray)
{
return s.str; // invalid, scope applies to struct members
return *s.strPtr; // valid, scope struct member is dereferenced
return sPtr.str; // valid, struct pointer is dereferenced
return *sPtr.strPtr; // valid, two pointers are dereferenced
return sarray[0]; // invalid, scope applies to static array elements
return sarray[1]; // invalid, ditto
return darray[0]; // valid, scope applies to array pointer, not elements
}
스코프 값 (Scope Values)
'스코프 값(scope value)'은 scope 변수의 값이거나, 스택에 할당된 메모리를 가리키는 생성된 값이에요. 그런 값은 정적 배열을 슬라이싱하거나 스택에 (있을 수 있는) 할당된 변수로 가는 포인터를 만들어서 생성돼요.
- 함수 매개변수
- 지역 변수
- 암묵적인
this매개변수를 통해 접근하는 struct 멤버 - Ref 함수의 반환값
Typesafe Variadic Functions의 가변 매개변수도 스코프 값이에요. 인자가 스택으로 전달되기 때문이에요.
지역 변수에 스코프 값이 할당되면, 변수에 명시적 타입이 있고 auto 키워드를 쓰지 않더라도 scope로 추론돼요.
@safe:
ref int[2] identity(return ref int[2] x) {return x;}
int* escape(int[2] y, scope int* z)
{
int x;
auto xPtr = &x; // inferred `scope int*`
int[] yArr = identity(y)[]; // inferred `scope int[]`
int* zCopy = z; // inferred `scope int*`
return zCopy; // error
}
void variadic(int[] a...)
{
int[] x = a; // inferred `scope int[]`
}
void main()
{
variadic(1, 2, 3);
}
struct S
{
int x;
// this method may be called on a stack-allocated instance of S
void f() @safe
{
int* p = &x; // inferred `scope int* p`
int* q = &this.x; // equivalent
}
}
@safe int[] foo() {
int[3] arr = [1,2,3];
int[] slice = arr[]; // slice picks up scope attribute
return slice; // error, returning scope variable `slice` is not allowed
}
int[] slice;
@safe void abc() {
int[3] arr = [1,2,3];
slice = arr[]; // error, assigning address of variable `arr` to `slice` with longer lifetime
}
Scope 매개변수는 scope 지역 변수와 같은 방식으로 취급되는데, 함수가 Function Attribute Inference를 가질 때 반환하는 것이 허용된다는 점만 달라요. 그 경우에는 Return Scope Parameters로 추론돼요.
scope 클래스 인스턴스 (scope Class Instances)
클래스 인스턴스를 직접 할당할 때 쓴 scope 변수는 RAII(Resource Acquisition Is Initialization) 프로토콜을 나타내요. 객체에 대한 참조가 스코프를 벗어날 때 객체의 소멸자(destructor)가 자동으로 호출된다는 뜻이에요. 예외로 스코프를 빠져나가도 소멸자는 호출돼요. 그래서 scope는 정리(cleanup)를 보장하는 데 쓰여요.
new로 클래스를 생성해 지역 scope 변수에 할당하면, 그 클래스는 스택에 할당될 수 있고 @nogc 문맥에서 허용돼요.
여러 scope 클래스 변수가 같은 지점에서 스코프를 벗어나면, 소멸자들은 변수가 생성된 순서의 역순으로 호출돼요.
클래스 타입의 scope 변수에 초기화가 아닌 할당(assignment)은 허용되지 않아요. 그 변수를 제대로 파괴하는 일이 복잡해지기 때문이에요.
import core.stdc.stdio : puts;
class C
{
~this() @nogc { puts(__FUNCTION__); }
}
void main() @nogc
{
{
scope c0 = new C(); // allocated on the stack
scope c1 = new C();
//c1 = c0; // Error: cannot rebind scope variables
// destructor of `c1` and `c0` are called here in that order
}
puts("bye");
}
OOP 애트리뷰트 (OOP Attributes)
abstract 애트리뷰트 (abstract Attribute)
abstract 클래스는 파생 클래스가 반드시 오버라이드해야 해요. abstract 멤버 함수를 선언하면 그 클래스가 abstract가 돼요.
final 애트리뷰트 (final Attribute)
- 클래스를
final로 선언하면 서브클래싱(subclassing)을 막을 수 있어요. - 클래스 메서드를
final로 선언하면 파생 클래스가 그것을 오버라이드하지 못하게 막을 수 있어요. - 인터페이스는 final 메서드를 정의할 수 있어요.
override 애트리뷰트 (override Attribute)
Virtual Functions를 참고하세요.
@mustuse 애트리뷰트 (@mustuse Attribute)
@mustuse 애트리뷰트는 D 런타임 모듈 core.attribute에 정의된, 컴파일러가 인식하는 UDA예요.
표현식이 '버려진다'(discarded)고 간주되는 경우는 다음 두 가지 중 하나가 참일 때예요.
ExpressionStatement의 최상위 표현식일 때, 또는- CommaExpression에서 콤마 왼쪽에 있는
AssignExpression일 때
다음 조건이 모두 참이면 그 표현식을 버리는 것은 컴파일 타임 오류예요.
- 할당 표현식(assignment expression), 증가 표현식(increment expression), 감소 표현식(decrement expression)이 아니고; 그리고
- 그 타입이
@mustuse로 표시된 struct 또는 union 타입의 선언일 때
'할당 표현식'은 단순 할당 표현식(simple assignment expression)이나 할당 연산자 표현식(assignment operator expression)을 의미해요.
'증가 표현식'은 연산자가 ++인 UnaryExpression 또는 PostfixExpression을 의미해요.
'감소 표현식'은 연산자가 --인 UnaryExpression 또는 PostfixExpression을 의미해요.
@mustuse를 함수 선언이나 struct·union 선언이 아닌 다른 집합체 선언에 붙이는 것은 컴파일 타임 오류예요. 이 규칙의 목적은 그런 용도를 향후 확장을 위해 남겨 두는 것이에요.
사용자 정의 애트리뷰트 (User-Defined Attributes)
UserDefinedAttribute:
@ ( TemplateArgumentList )
@ TemplateSingleArgument
@ Identifier ( NamedArgumentListopt )
@ TemplateInstance
@ TemplateInstance ( NamedArgumentListopt )
사용자 정의 애트리뷰트(User-Defined Attributes, UDA)는 선언에 붙일 수 있는 컴파일 타임 주석이에요. 이 애트리뷰트들은 컴파일 타임에 조회하고, 추출하고, 조작할 수 있어요. 런타임 구성 요소는 없어요.
- 하나 이상의
TemplateArguments목록 - (위 문법에 맞는) 컴파일 타임 표현식
- 심볼 식별자
- 컴파일 타임에 인자 목록으로 호출할 (위 문법에 맞는) 표현식
@3 int a; // value attribute
@("string", 7) int b; // multiple values
// using compile-time constant
enum val = 3;
@val int a2; // has same attribute as `a`
enum Foo;
@Foo int c; // type name attribute
struct Bar
{
int x;
}
@Bar() int d; // type instance attribute
@Bar(3) int e; // type instance attribute using initializer
e의 경우, 애트리뷰트는 인자를 사용해 정적으로 초기화(struct Bar)된 struct의 인스턴스예요.
한 선언에 대해 스코프에 여러 UDA가 있으면, 그것들은 이어 붙여져요(concatenate).
@(1)
{
@(2) int a; // has UDAs (1, 2)
@("string") int b; // has UDAs (1, "string")
}
함수 매개변수는 UDA를 가질 수 있어요. 매개변수는 매개변수 목록 밖에서 UDA를 상속받지 않아요.
@2 void f(@3 int p)
{
pragma(msg, __traits(getAttributes, p)); // prints AliasSeq!(3)
}
__traits(getAttributes)
UDA는 __traits를 사용해 컴파일 타임 시퀀스로 추출할 수 있어요.
@('c') string s;
pragma(msg, __traits(getAttributes, s)); // prints AliasSeq!('c')
그 심볼에 사용자 정의 애트리뷰트가 없으면 빈 시퀀스가 반환돼요. 결과는 다른 컴파일 타임 시퀀스처럼 사용할 수 있어요. 인덱싱하거나 템플릿 매개변수로 넘기는 등의 일이 가능하죠.
enum e = 7;
@("hello") struct SSS { }
@(3)
{
@(4) @e @SSS int foo;
}
alias TP = __traits(getAttributes, foo);
pragma(msg, TP); // prints AliasSeq!(3, 4, 7, (SSS))
pragma(msg, TP[2]); // prints 7
시퀀스 안의 어떤 타입이든 선언하는 데 사용할 수 있어요.
TP[3] a; // a is declared as an SSS
타입 이름의 애트리뷰트는 변수의 애트리뷰트와 같지 않아요.
pragma(msg, __traits(getAttributes, a)); // prints AliasSeq!()
pragma(msg, __traits(getAttributes, typeof(a))); // prints AliasSeq!("hello")
용법 (Usage)
물론, UDA의 진짜 가치는 특정 값을 가진 사용자 정의 타입을 만들 수 있다는 점이에요. 기본 타입의 애트리뷰트 값만으로는 확장성이 없어요.
애트리뷰트가 값인지 심볼인지는 사용자 몫이고, 이후 애트리뷰트가 이전 것을 누적할지 덮어쓸지도 사용자가 어떻게 해석하느냐에 달려 있어요.
템플릿 (Templates)
UDA가 템플릿 선언에 붙어 있으면, 그 템플릿 인스턴스의 모든 직접 멤버에도 자동으로 붙어요. 그 멤버들 중 일부가 템플릿이면 재귀적으로 적용돼요.
@("foo") template Outer(T)
{
struct S
{
int x;
}
int y;
void fun() {}
@("bar") template Inner(U)
{
int z;
}
}
pragma(msg, __traits(getAttributes, Outer!int.S));
// prints AliasSeq!("foo")
pragma(msg, __traits(getAttributes, Outer!int.S.x));
// prints AliasSeq!()
pragma(msg, __traits(getAttributes, Outer!int.y));
// prints AliasSeq!("foo")
pragma(msg, __traits(getAttributes, Outer!int.fun));
// prints AliasSeq!("foo")
pragma(msg, __traits(getAttributes, Outer!int.Inner));
// prints AliasSeq!("foo", "bar")
pragma(msg, __traits(getAttributes, Outer!int.Inner!int.z));
// prints AliasSeq!("foo", "bar")
UDA는 템플릿 매개변수에는 붙일 수 없어요.
더 알아보기 (Learn more)
- D 언어 사양 — 전체 목차에서 각 챕터를 이어서 볼 수 있어요.
- Properties와 Pragmas 장이 이 챕터의 앞뒤에 있어요.
- 애트리뷰트의 하위 개념들은 해당 주제의 전용 문서로 연결돼 있어요. 함수 수준 애트리뷰트(
@safe,@nogc,pure,nothrow,@property)는 Function Safety, No-GC, Pure, Nothrow 문서에서, visibility는 Module 문서에서 더 자세히 다뤄져요. - 사용자 정의 애트리뷰트의 실제 사용 사례는 DIP18과 D 런타임의
core.attribute모듈을 살펴보면 좋아요.