Coaspe

Flutter - Const construtor를 사용하는 이유 본문

Flutter/번역

Flutter - Const construtor를 사용하는 이유

Coaspe 2023. 2. 17. 14:04

https://stackoverflow.com/questions/21744677/how-does-the-const-constructor-actually-work#:~:text=In%20order%20to%20use%20a,a%20compile-time%20constant%20value.

 

How does the const constructor actually work?

I've noticed it's possible to create a const constructor in Dart. In the documentation, it says that const word is used to denote something a compile time constant. I was wondering what happens w...

stackoverflow.com

를 번역한 글 입니다.


meznoni's reply

Const 생성자는 "Canonicalized"(표준, 기준이 되는) 인스턴스를 생성합니다.

 

모든 Const 표현은 기준이 되고, 후에 기준이 된 심볼들은 상수들의 동등성을 확인하기 위해 사용됩니다.

 

Canonicalization:

Canonicalization은 하나 이상의 표현을 지니는 데이터를 "표준", 기준이 되는 표현을 가지도록 변환하는 과정 입니다.

이 과정은 다른 표현들이 동등성을 가지는지 확인하여 이루어 질 수 있습니다.

그리고 그 결과는 구분이 되는 데이터 구조의 수를 카운트하거나, 반복되는 계산을 줄여 다양한 알고리즘의 성능을 향상시키거나 의미있는 정렬 순서를 부여 할 수 있게 하는 데 사용 됩니다.


const Foo(1, 1) 같은 const 표현들은 가상 머신이 비교 연산을 수행 할 때 유용하고 사용 가능한 형태를 대표한다는 것을 의미합니다.

 

가상 머신은 const 표현에서 발생한 값의 타입과 인자들만 신경쓰면 됩니다. 그리고 당연히 그것들은 최적화를 위해 축소됩니다.

 

동일한 canonicalized 값을 가지는 상수들:

var foo1 = const Foo(1, 1)
var foo2 = const Foo(1, 1);

다른 canonicalized 값을 가지는 상수들

var foo3 = const Foo(1, 2);
var foo4 = const Foo(1, 3);

var baz1 = const Baz(const Foo(1, 1), "hello");
var baz2 = const Baz(const Foo(1, 2), "hello");

상수는 매번 새로 만들어지지 않습니다. 상수들은 컴파일 타임에 canonicalized되고 canonical 시그니쳐로 해쉬되어 있는 룩업 테이블에 저장됩니다.


Gunter Zochbauer's reply (Gunter Zochbauer이 분도 Chris Storms 블로그에서 Lasse님의 답변을 가져왔다고 하네요. Dart Constant Constructors 해당 링크입니다.)

 

모든 클래스는 const 생성자 여부와 관계없이 final 필드를 가질 수 있습니다.

Dart의 필드는 사실 필드를 읽고 업데이트하며 자동으로 생성되는 getter와 setter 가 결합된 익명 저장 공간 입니다. 그리고 필드들은 생성자의 initializer list에서 초기화 될 수 있습니다.

final 필드도 setter가 없다는 것만 제외하고 동일하며, setter가 없기 때문에 값을 설정하기 위해서는 생성자 initializer list로 선언 해야하고 그 이후에 값을 바꿀 수 없습니다. 그게 "final"이라는 이름의 이유이죠.

const 생성자의 포인트는 final 필드들을 초기화하는 데 있지 않습니다. 그 작업은 다른 생성자들도 할 수 있거든요. 그 포인트는 바로 어떠한 구문의 실행 없이 모든 필드 값들이 컴파일 타임에 알 수 있는 상수인 컴파일 타임 상수 값을 생성하는 데 있습니다.

그건 클래스나 생성자에 몇가지 제한을 둡니다.
1. const 생성자는 바디를 가질 수 없습니다.(어떤 구문도 실행 될 수 없습니다.)
2. 해당 클래스는 final이 아닌 필드를 가질 수 없습니다.(컴파일 타임에 우리가 알고 있는 값들은 나중에 절대 바뀔 수 없습니다.)

그리고 initializer list는 반드시 다른 컴파일 타임 상수로만 필드를 초기화해야 합니다. 그래서 생성자의 오른쪽 사이드는 "컴파일 타임 상수 표현식"[1]으로 제한됩니다. 그리고 이것은 "const"로 프리픽스(접두) 되어야 합니다. 그렇지 않으면 위의 요구 사항을 만족시키는 평범한 생성자를 얻습니다. 평범한 생성자를 얻는 건 상관 없지만, const 생성자가 아닙니다.

const 생성자를 컴파일 타임 상수 객체를 생성하는 데 사용하고 싶다면, 인스턴스를 선언할 때 사용하는 "new" 표현 대신 "const"를 사용하세요. const 생성자에 "new"를 사용하는 것은 가능하지만, 그건 컴파일 타임 상숫값이 아닌 평범하며 새로운 객체를 생성합니다. 그건 const 생성자로 일반 생성자가 런타임에 객체를 생성하는 것처럼 행동하는 것과, 컴파일 타임에 컴파일 타임 상수를 생성하는 것 모두 가능하다는 것을 의미합니다.
다음은 예제를 봅시다.
class Point { 
  static final Point ORIGIN = const Point(0, 0); 
  final int x; 
  final int y; 
  const Point(this.x, this.y);
  Point.clone(Point other): x = other.x, y = other.y; //[2] 
}

main() { 
  // Assign compile-time constant to p0. 
  Point p0 = Point.ORIGIN; 
  // Create new point using const constructor. 
  Point p1 = new Point(0, 0); 
  // Create new point using non-const constructor.
  Point p2 = new Point.clone(p0); 
  // Assign (the same) compile-time constant to p3. 
  Point p3 = const Point(0, 0); 
  print(identical(p0, p1)); // false 
  print(identical(p0, p2)); // false 
  print(identical(p0, p3)); // true! 
}​
컴파일 타임 상수는 canonicalized(기준,표준화) 되어 있습니다. 그건 우리가 "const Point(0, 0)"을 몇번을 선언해도 항상 같은 객체를 생성한다는 것을 의미합니다. 그런 속성은 유용하지만, 값을 홀드하기 위해 변수를 만들고 대신 변수를 사용할 수 있기 때문에 생각만큼 유용하진 않습니다.

그래서 컴파일 타임 상수는 어떤 용도로 사용해야 할까요?
1. enums에 사용하면 유용합니다.
2. switch cases에서 컴파일 타임 상수를 사용하세요.
3. annotations으로 사용하세요.
컴파일 타임 상수는 Dart가 느린 초기화(lazily initializing) 변수로 전환하기 전에 더 중요했습니다. 그것 이전에  만약 "foo"가 컴파일 타임 상수라면, 초기화 된 전역 변수를 선언하는 유일한 방법은 "var x = foo;" 같이 하는 것 뿐이 었습니다. 그런 요구 사항 없이, 대부분의 프로그램은 const 객체를 사용하지 않고 코딩되었습니다.

짧게 요약하면: const 생성자는 컴파일 타임 상수 값을 생성하기 위해 존재합니다.

[1] 이건 생성자의 매개변수들을 참조하기 때문에 [1]이 아니라면 "잠재적인 컴파일 타임 상수 표현식"입니다. 
[2] 와 같이 클래스는 const 생성자와 const가 아닌 생성자 모두 가질 수 있습니다.

 

Comments