속성
속성 (Attributes)
속성은 하나 이상의 선언을 수정하는 방법이에요. 일반적인 형식은 다음과 같아요.
출처: Attributes
본문
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를 참고하세요.
문법 (Grammar)
[AttributeSpecifier]():
[*Attribute*](#Attribute) :
[*Attribute*](#Attribute) [*DeclarationBlock*](#DeclarationBlock)
[Attribute]():
[*AlignAttribute*](#AlignAttribute)
[*AtAttribute*](#AtAttribute)
[*DeprecatedAttribute*](#DeprecatedAttribute)
[*FunctionAttributeKwd*](#FunctionAttributeKwd)
[*LinkageAttribute*](#LinkageAttribute)
[*Pragma*](../spec/pragma.html#Pragma)
[*VisibilityAttribute*](#VisibilityAttribute)
[abstract](#abstract)
[auto](#auto)
[const](#const)
[final](#final)
[__gshared](#gshared)
[extern](#linkage)
[immutable](#immutable)
[inout](#inout)
[override](#override)
[ref](#ref)
[__rvalue](#__rvalue)
[scope](#scope)
[shared](#shared)
[static](#static)
[synchronized](#synchronized)
[FunctionAttributeKwd]():
[nothrow](#nothrow)
[pure](#pure)
[AtAttribute]():
@ [disable](#disable)
@ [__future](#future)
@ [__ctfe](#ctfeonly)
@ [nogc](#nogc)
@ [live](../spec/ob.html)
[*Property*](#Property)
@ [safe](#safe)
@ [system](#safe)
@ [trusted](#safe)
[*UserDefinedAttribute*](#UserDefinedAttribute)
[Property]():
@ [property](#property)
[DeclarationBlock]():
[*DeclDef*](../spec/module.html#DeclDef)
{ [*DeclDefs*](../spec/module.html#DeclDefs)opt }
링키지 속성 (Linkage Attribute)
[LinkageAttribute]():
extern ( [*LinkageType*](#LinkageType) )
extern ( C ++ , )
extern ( C ++ , [*QualifiedIdentifier*](../spec/type.html#QualifiedIdentifier) )
extern ( C ++ , [*NamespaceList*](#NamespaceList) )
extern ( C ++ , class )
extern ( C ++ , struct )
[LinkageType]():
C
C ++
D
Windows
System
Objective - C
[NamespaceList]():
[*ConditionalExpression*](../spec/expression.html#ConditionalExpression)
[*ConditionalExpression*](../spec/expression.html#ConditionalExpression) ,
[*ConditionalExpression*](../spec/expression.html#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와 같아요.
구현 참고: Win32 플랫폼에서는 Windows가 존재해야 해요.
C 함수 호출 규약은 다음과 같이 지정돼요.
extern (C):
int foo(); // call foo() with C conventions
extern(C)는 struct나 class를 포함한 모든 타입의 선언에 제공될 수 있지만, C 쪽에는 대응하는 것이 없다는 점에 주의하세요. 그 경우 속성은 무시돼요. 이 동작은 중첩 함수와 중첩 변수에도 적용돼요. 그러나 static 멤버 메서드와 static 중첩 함수의 경우, extern(C)를 추가하면 호출 규약은 바뀌지만 맹글링은 바뀌지 않아요.
D 규약은 다음과 같아요.
extern (D):
Windows API 규약은 다음과 같아요.
extern (Windows):
void *VirtualAlloc(
void *lpAddress,
uint dwSize,
uint flAllocationType,
uint flProtect
);
Windows 규약은 C 규약과 Win32 플랫폼에서만 구별되며, 거기서는 stdcall 규약과 동일해요.
단독 extern 키워드는 저장 클래스로 사용된다는 점에 주의하세요.
C++ 네임스페이스 (C++ Namespaces)
링키지 형식 extern (C++, QualifiedIdentifier)는 C++ 네임스페이스에 존재하는 C++ 선언을 만들어요. QualifiedIdentifier 는 네임스페이스를 지정해요.
extern (C++, N) { void foo(); }
는 다음 C++ 선언을 가리켜요.
namespace N { void foo(); }
그리고 한정 있거나 없이 참조할 수 있어요.
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*](../spec/expression.html#AssignExpression) )
다음의 정렬(alignment)을 지정해요.
- 변수
- struct 필드
- union 필드
- class 필드
- struct, union, class 타입
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 컴파일러의 동작과 일치하는 정렬을 지정해요. 이는 음수가 아닌 2의 거듭제곱이어야 해요.
값 1은 정렬이 수행되지 않는다는 뜻이며, 필드가 함께 채워져요(packed).
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, union, class에 들어갈 때 기본값으로 재설정되고, 그 스코프에서 나올 때 복원돼요. 기본 클래스에서 상속되지는 않아요.
참고: 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
}
정의되지 않은 동작(Undefined Behavior): GC 할당 객체에 대한 포인터와 참조가 size_t 바이트 경계에 정렬되지 않은 경우.
deprecated 속성 (deprecated Attribute)
[DeprecatedAttribute]():
deprecated
deprecated ( [*AssignExpression*](../spec/expression.html#AssignExpression) )
라이브러리에서 기능을 폐기하면서도 하위 호환성을 위해 유지해야 하는 경우가 많아요. 그러한 선언은 deprecated로 표시할 수 있으며, 이는 어떤 코드가 deprecated 선언을 참조하면 컴파일러가 오류를 생성하도록 지시할 수 있다는 뜻이에요.
deprecated
{
void oldFoo();
}
oldFoo(); // Deprecated: function test.oldFoo is deprecated
선택적으로 문자열 리터럴이나 매니페스트 상수(manifest constant)를 사용해 폐기 메시지에 추가 정보를 제공할 수 있어요.
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
}
구현 참고: 컴파일러는 deprecated를 무시할지, 경고를 발생시킬지, 아니면 컴파일 중 오류를 발생시킬지 지정하는 스위치를 가져야 해요.
@__future 속성
@__future 속성은 다른 모듈에서 이름 충돌(name clash)을 일으킬 수 있는 새 심볼을 표시하는 데 사용돼요. 같은 이름의 심볼을 사용하는 다른 모듈은 이전처럼 계속 컴파일되지만 폐기 메시지를 보여줘요. 그 메시지는 새 심볼이 미래에 기존 코드를 깨뜨릴 것임을 나타내며, 다른 모듈을 업데이트해야 함을 알려줘요.
참고: 현재는 @__future 기본 클래스 메서드를 암시적으로 오버라이드하는 메서드만 폐기 메시지를 보여줘요.
자세한 내용은 DIP1007을 참고하세요.
@__ctfe 속성
@__ctfe 속성은 함수가 CTFE로만 호출되도록 보장하며 객체 코드 생성을 비활성화해요. 이는 명령행 플래그에서 비롯된 코드 생성 제한을 우회해요. @__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
}
참고: 상속과의 상호작용 때문에 가상 메서드는 @__ctfe로 표시할 수 없어요.
가시성 속성 (Visibility Attributes)
[VisibilityAttribute]():
export
package
package ( [*QualifiedIdentifier*](../spec/type.html#QualifiedIdentifier) )
private
protected
public
가시성은 private, package, protected, public, export 중 하나인 속성이에요. DIP22보다 이전의 문서에서는 보호(protection) 속성이라고 부르기도 해요.
가시성은 심볼 이름 조회에 참여해요.
export 속성
export는 심볼이 실행 파일, 공유 라이브러리 또는 DLL 외부에서 접근될 수 있음을 의미해요. 그 심볼은 실행 파일, 공유 라이브러리 또는 DLL에서 정의된 곳에서 export 되고, 다른 실행 파일, 공유 라이브러리 또는 DLL에 의해 import 된다고 말해요.
심볼의 정의에 적용된 export는 그 심볼을 export해요. 심볼의 선언에 적용된 export는 그 심볼을 import해요. 변수는 extern이 적용되지 않는 한 정의(definition)예요.
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`
본문이 있는 함수는 정의이고, 본문이 없는 함수는 선언이에요.
export void f() { } // definition, exporting `f`
export void g(); // declaration, importing `g`
Windows 용어로 dllexport 는 DLL에서 심볼을 export하는 것을, dllimport 는 DLL 또는 실행 파일이 DLL에서 심볼을 import하는 것을 의미해요.
package 속성
package는 private를 확장해서, 같은 패키지 안의 다른 모듈에 있는 코드가 package 멤버에 접근할 수 있게 해 줘요. 식별자가 제공되지 않으면 이는 가장 안쪽 패키지에만 적용되며, 모듈이 패키지에 중첩되어 있지 않으면 private로 기본 설정돼요.
package는 점으로 구분된 식별자 목록 형태의 선택적 매개변수를 가질 수 있으며, 이는 한정된 패키지 이름으로 해석돼요. 패키지는 모듈의 부모 패키지 또는 그 조상 중 하나여야 해요. 이 매개변수가 있으면 심볼은 지정된 패키지와 그 모든 하위 패키지에서 보이게 돼요.
private 속성
private 가시성을 가진 심볼은 같은 모듈 안에서만 접근할 수 있어요. private 멤버 함수는 암시적으로 final이며 오버라이드할 수 없어요.
protected 속성
protected는 클래스 안에서만 적용되며(믹스인될 수 있으므로 템플릿도), 심볼이 같은 모듈의 멤버나 파생 클래스에만 보인다는 뜻이에요. 파생 클래스 멤버 함수를 통해 protected 인스턴스 멤버에 접근할 때, 그 멤버는 'this'와 같은 타입으로 암시적으로 캐스팅될 수 있는 객체 인스턴스에 대해서만 접근할 수 있어요. protected 모듈 멤버는 불법이에요.
public 속성
public은 실행 파일 내의 어떤 코드든 그 멤버를 볼 수 있다는 뜻이에요. 기본 가시성 속성이에요.
변경 가능성 속성 (Mutability Attributes)
const 속성
const 타입 한정자는 선언된 심볼의 타입을 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 속성은 const가 하는 것과 같은 방식으로 타입을 T에서 immutable(T)로 수정해요. 다음을 참고하세요.
inout 속성
inout 속성은 const가 하는 것과 같은 방식으로 타입을 T에서 inout(T)로 수정해요.
공유 저장 속성 (Shared Storage Attributes)
shared 속성
shared를 참고하세요.
__gshared 속성
기본적으로 immutable이 아닌 전역 선언은 스레드 로컬 저장 공간에 존재해요. 전역 변수가 __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.
}
경고: shared 속성과 달리 __gshared은 데이터 경쟁이나 기타 다중 스레드 동기화 문제에 대한 보호 장치를 제공하지 않아요. __gshared으로 표시된 변수에 대한 접근이 올바르게 동기화되도록 보장하는 것은 프로그래머의 책임이에요.
__gshared은 @safe 코드에서 허용되지 않아요.
synchronized 속성
Synchronized Methods를 참고하세요.
@disable 속성
다음 선언들은 @disable 속성을 사용해 비활성화할 수 있어요.
- 함수, Constructor, NewDeclaration
- 변수, 매니페스트 상수, EnumMember
비활성화된 선언을 사용하면 컴파일 타임 오류가 발생해요.
모범 사례(Best Practices): 런타임 오류를 생성하는 함수에 의존하는 대신 @disable을 사용해 특정 연산이나 오버로드를 컴파일 타임에 명시적으로 허용하지 마세요.
@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(); in a struct is disallowed default initialization. — struct 안의 @disable this();는 기본 초기화를 허용하지 않아요.
- struct 복사 생성자 비활성화는 struct를 복사할 수 없게 해요.
- struct 이동 생성자 비활성화는 struct를 이동할 수 없게 해요.
@safe, @trusted, @system 속성
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 속성
No-GC Functions를 참고하세요.
@property 속성
Property Functions를 참고하세요.
nothrow 속성
Nothrow Functions를 참고하세요.
pure 속성
Pure Functions를 참고하세요.
ref 속성
ref Storage Class를 참고하세요.
return 속성
__rvalue 속성
static 속성
static 속성은 타입, 함수, 변수에 적용돼요. 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 속성은 다른 속성이 없고 타입 추론이 필요할 때 사용돼요.
auto i = 6.8; // declare i as a double
함수의 경우 auto 속성은 반환 타입 추론을 의미해요. Auto Functions를 참고하세요.
scope 속성
scope 속성은 변수의 포인터 값이 변수가 선언된 스코프를 벗어나지 않을 것임을 나타내요.
변수의 타입에 간접 참조(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
}
간접 참조를 가진 타입의 로컬 변수에 적용되면, 그 값은 더 긴 수명을 가진 변수에 할당되지 못할 수 있어요.
- 변수가 선언된 Scope 문 밖의 변수
- 로컬 변수는 선언된 역순으로 소멸되므로 scope 변수보다 먼저 선언된 변수
- __gshared 또는 static 변수
그들을 암시적으로 더 긴 수명의 변수에 할당하는 다른 연산도 허용되지 않아요.
- 함수에서 scope 변수 반환
- 함수를 호출해 scope 변수를 non-scope 매개변수에 할당
- scope 변수를 배열 리터럴에 넣기
참고: 탈출 분석(escape analysis)은 @safe 코드에서만 수행돼요. -preview=dip1000 스위치도 활성화되어야 해요.
scope 속성은 타입이 아니라 변수 선언의 일부이며, 첫 번째 수준의 간접 참조에만 적용돼요. 예를 들어 scope 포인터의 동적 배열로 변수를 선언하는 것은 불가능해요. scope는 배열 자체의 .ptr에만 적용되고 그 요소에는 적용되지 않기 때문이에요. scope는 다양한 타입에 다음과 같이 영향을 줘요.
| 로컬 변수 타입 | scope가 적용되는 것 | | 모든 기본 데이터 타입 | 없음 | | 포인터 T* | 포인터 값 | | 동적 배열 T[] | 요소에 대한 .ptr | | 정적 배열 T[n] | 각 요소 T | | [연관 배열](../spec/hash-map.html#Associative Array) K[V] | 구현 정의 구조체에 대한 포인터 | | struct 또는 union | 각 멤버 변수 | | 함수 포인터 | 포인터 값 | | delegate | .funcptr과 .ptr(클로저 컨텍스트) 포인터 값 모두 | | class 또는 interface | 클래스 참조 | | enum | 기본 타입 |
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 값 (Scope Values)
"scope 값"은 scope 변수의 값이거나, 스택에 할당된 메모리를 가리키는 생성된 값이에요. 이러한 값은 정적 배열을 슬라이싱하거나, (또는 있을 수 있는) 스택에 할당된 변수에 포인터를 만들어 생성돼요.
- 함수 매개변수
- 로컬 변수
- 암시적 this 매개변수를 통해 접근되는 struct 멤버
- Ref 함수의 반환 값
Typesafe 가변 인자 함수의 가변 인자 매개변수도 scope 값이에요. 인자가 스택에 전달되기 때문이에요.
로컬 변수에 scope 값이 할당되면, 변수가 명시적 타입을 갖고 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 로컬 변수와 동일하게 취급되지만, 함수가 함수 속성 추론을 가질 때 반환하는 것이 허용돼요. 그 경우 Return Scope 매개변수로 추론돼요.
scope 클래스 인스턴스 (scope Class Instances)
클래스 인스턴스를 직접 할당할 때 사용되면, scope 변수는 RAII(Resource Acquisition Is Initialization) 프로토콜을 나타내요. 이는 객체에 대한 참조가 스코프를 벗어날 때 객체의 소멸자가 자동으로 호출된다는 뜻이에요. 소멸자는 스코프가 예외를 통해 종료되더라도 호출되므로, scope은 정리(cleanup)를 보장하는 데 사용돼요.
클래스가 new로 구성되어 로컬 scope 변수에 할당되면, 스택에 할당될 수 있고 @nogc 컨텍스트에서 허용될 수 있어요.
같은 지점에서 둘 이상의 scope 클래스 변수가 스코프를 벗어나면, 소멸자는 변수가 구성된 역순으로 호출돼요.
초기화 외에 클래스 타입의 scope 변수에 할당하는 것은 허용되지 않아요. 변수의 올바른 소멸을 복잡하게 만들기 때문이에요.
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 클래스는 파생 클래스에 의해 오버라이드되어야 해요. abstract 멤버 함수를 선언하면 클래스가 abstract가 돼요.
final 속성
- 클래스는 서브클래싱을 막기 위해 final로 선언할 수 있어요.
- 클래스 메서드는 파생 클래스가 오버라이드하지 못하도록 final로 선언할 수 있어요.
- 인터페이스는 final 메서드를 정의할 수 있어요.
override 속성
Virtual Functions를 참고하세요.
@mustuse 속성
@mustuse 속성은 D 런타임 모듈 core.attribute에 정의된 컴파일러 인식 UDA예요.
다음 중 하나가 참일 때에만 표현식이 폐기(discarded)된 것으로 간주돼요.
- ExpressionStatement의 최상위 Expression이거나,
- CommaExpression에서 쉼표의 왼쪽에 있는 AssignExpression이거나.
다음이 모두 참이면 표현식을 폐기하는 것은 컴파일 타임 오류예요.
- 할당 표현식, 증가 표현식, 감소 표현식이 아니고; 그리고
- 그 타입이 @mustuse로 주석 처리된 선언의 struct 또는 union 타입이고.
"할당 표현식"이란 단순 할당 표현식 또는 할당 연산자 표현식을 의미해요.
"증가 표현식"이란 연산자가 ++인 UnaryExpression 또는 PostfixExpression을 의미해요.
"감소 표현식"이란 연산자가 --인 UnaryExpression 또는 PostfixExpression을 의미해요.
함수 선언 또는 struct나 union 선언 이외의 집계 선언에 @mustuse를 붙이는 것은 컴파일 타임 오류예요. 이 규칙의 목적은 그러한 사용을 향후 확장을 위해 예약하는 것이에요.
사용자 정의 속성 (User-Defined Attributes)
[UserDefinedAttribute]():
@ ( [*TemplateArgumentList*](../spec/template.html#TemplateArgumentList) )
@ [*TemplateSingleArgument*](../spec/template.html#TemplateSingleArgument)
@ [*Identifier*](../spec/lex.html#Identifier) ( [*NamedArgumentList*](../spec/expression.html#NamedArgumentList)opt )
@ [*TemplateInstance*](../spec/template.html#TemplateInstance)
@ [*TemplateInstance*](../spec/template.html#TemplateInstance) ( [*NamedArgumentList*](../spec/expression.html#NamedArgumentList)opt )
사용자 정의 속성(User-Defined Attributes, UDAs)은 선언에 붙일 수 있는 컴파일 타임 주석이에요. 이 속성들은 컴파일 타임에 조회, 추출, 조작할 수 있어요. 런타임 구성 요소는 없어요.
사용자 정의 속성은 다음을 사용해 정의돼요.
- 하나 이상의 TemplateArgument 목록
- 컴파일 타임 표현식 (위 문법과 일치)
- 심볼 식별자
- 컴파일 타임에 인자 목록과 함께 호출할 표현식 (위 문법과 일치)
@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의 인스턴스이며 인자를 사용해 정적으로 초기화된 것이에요.
선언에 대해 여러 UDA가 스코프에 있으면 그것들은 연결(concatenated)돼요.
@(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)
UDAs는 __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의 진정한 가치는 특정 값을 가진 사용자 정의 타입을 만들 수 있다는 점이에요. 기본 타입의 속성 값만으로는 확장이 어려워요.
속성이 값인지 심볼인지는 사용자에게 달려 있고, 이후 속성이 이전 속성을 누적(accumulate)할지 덮어쓸지(override)도 사용자가 어떻게 해석하느냐에 달려 있어요.
템플릿 (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")
UDAs는 템플릿 매개변수에 붙일 수 없어요.
더 알아보기
- Properties — 프로퍼티
- Pragmas — 프라그마