답변:
들어 POD의 클래스 멤버, 그것은 스타일의 단지 문제, 차이가 없습니다. 클래스 인 클래스 멤버의 경우 기본 생성자를 불필요하게 호출하지 않습니다. 치다:
class A
{
public:
A() { x = 0; }
A(int x_) { x = x_; }
int x;
};
class B
{
public:
B()
{
a.x = 3;
}
private:
A a;
};
이 경우에 대한 생성자 B
는에 대한 기본 생성자를 호출 A
한 다음 a.x
3으로 초기화 합니다. 더 좋은 방법은 의 생성자가 초기화 목록에서 B
의 생성자를 직접 호출 A
하는 것입니다.
B()
: a(3)
{
}
이것은 기본 생성자가 아닌 A
의 A(int)
생성자를 호출 합니다. 이 예제에서 그 차이는 무시할 만하지 만 A
메모리를 할당하거나 파일을 여는 등의 기본 생성자가 더 많은 작업 을 수행한다고 가정 해보십시오 . 불필요하게 그렇게하고 싶지 않을 것입니다.
클래스는 기본 생성자가없는, 또는 당신이있는 경우 또한, const
멤버 변수를, 당신은 해야한다 이니셜 라이저 목록을 사용하여 :
class A
{
public:
A(int x_) { x = x_; }
int x;
};
class B
{
public:
B() : a(3), y(2) // 'a' and 'y' MUST be initialized in an initializer list;
{ // it is an error not to do so
}
private:
A a;
const int y;
};
위에서 언급 한 성능상의 이유 외에도 클래스가 생성자 매개 변수로 전달 된 객체에 대한 참조를 저장하거나 클래스에 const 변수가있는 경우 초기화 목록을 사용하는 것 외에는 선택할 수 없습니다.
답변에 언급되지 않은 생성자 이니셜 라이저 목록을 사용하는 중요한 이유 중 하나는 기본 클래스의 초기화입니다.
건설 순서에 따라 기본 클래스는 하위 클래스보다 먼저 구성되어야합니다. 생성자 이니셜 라이저 목록이 없으면 기본 클래스에 기본 생성자가 있으면 자식 클래스의 생성자를 입력하기 직전에 호출됩니다.
그러나 기본 클래스에 매개 변수화 된 생성자 만있는 경우 생성자 이니셜 라이저 목록을 사용하여 기본 클래스가 자식 클래스보다 먼저 초기화되도록해야합니다.
매개 변수화 된 생성자 만있는 서브 오브젝트의 초기화
능률
생성자 이니셜 라이저 목록을 사용하면 먼저 데이터 멤버를 기본 상태로 초기화 한 다음 상태를 코드에서 필요한 상태로 변경하지 않고 코드에서 필요한 상태로 데이터 멤버를 초기화합니다.
클래스의 정적이 아닌 const 데이터 멤버에 기본 생성자가 있고 생성자 이니셜 라이저 목록을 사용하지 않으면 기본 상태로 초기화되므로 의도 한 상태로 초기화 할 수 없습니다.
참조가 나중에 선언되고 초기화 될 수 없으므로 컴파일러가 생성자를 입력 할 때 참조 데이터 멤버를 초기화해야합니다. 이것은 생성자 이니셜 라이저 목록에서만 가능합니다.
성능 문제 외에도 코드 유지 관리 및 확장 성이라고 부르는 또 다른 중요한 문제가 있습니다.
T가 POD이고 초기화 목록을 선호하기 시작하면 한 번 T가 비 POD 유형으로 변경되면 불필요한 최적화 생성자 호출이 이미 최적화되어 있기 때문에 초기화 주위에서 아무것도 변경하지 않아도됩니다.
유형 T에 기본 생성자와 하나 이상의 사용자 정의 생성자가 있고 기본 생성자를 제거하거나 숨기려고 한 경우 초기화 목록이 사용 된 경우 사용자 정의 생성자가 코드를 업데이트 할 필요가 없습니다. 그들은 이미 올바르게 구현되었습니다.
const 멤버 또는 참조 멤버와 동일하게 처음에 T가 다음과 같이 정의되었다고 가정 해 봅시다.
struct T
{
T() { a = 5; }
private:
int a;
};
다음으로, 처음부터 초기화 목록을 사용하는 경우 const로 한정하기로 결정한 경우 한 줄 변경이지만 위와 같이 T를 정의한 경우 할당자를 제거하기 위해 생성자 정의를 파헤쳐 야합니다.
struct T
{
T() : a(5) {} // 2. that requires changes here too
private:
const int a; // 1. one line change
};
코드를 "코드 원숭이"가 아니라 자신이하고있는 일에 대해 더 깊이 고려하여 결정을 내리는 엔지니어가 코드를 작성하면 유지 관리가 훨씬 쉽고 오류가 덜 발생한다는 사실은 비밀이 아닙니다.
// Without Initializer List
class MyClass {
Type variable;
public:
MyClass(Type a) { // Assume that Type is an already
// declared class and it has appropriate
// constructors and operators
variable = a;
}
};
여기서 컴파일러는 다음 단계에 따라 MyClass
1 유형의 객체를 만듭니다 . "a"에 대해 유형의 생성자가 먼저 호출됩니다.
2.“Type”의 할당 연산자는 MyClass () 생성자의 본문 내에서 호출됩니다.
variable = a;
그리고 마지막으로“Type”의 소멸자는 범위를 벗어나기 때문에“a”가 필요합니다.
이제 Initializer List가있는 MyClass () 생성자와 동일한 코드를 고려하십시오.
// With Initializer List
class MyClass {
Type variable;
public:
MyClass(Type a):variable(a) { // Assume that Type is an already
// declared class and it has appropriate
// constructors and operators
}
};
이니셜 라이저 목록을 사용하면 다음 단계에 따라 컴파일러가 이어집니다.
추가 정보를 추가하여 멤버 초기화 목록이 얼마나 차이가 나는지 보여줄 수 있습니다 . leetcode 303 범위 합계 쿼리-불변, https://leetcode.com/problems/range-sum-query-immutable/ 에서 특정 크기의 벡터를 0으로 구성하고 초기화해야합니다. 다음은 두 가지 다른 구현 및 속도 비교입니다.
멤버 초기화 목록이 없으면 AC를 얻으려면 약 212ms가 소요 됩니다.
class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) {
preSum = vector<int>(nums.size()+1, 0);
int ps = 0;
for (int i = 0; i < nums.size(); i++)
{
ps += nums[i];
preSum[i+1] = ps;
}
}
int sumRange(int i, int j) {
return preSum[j+1] - preSum[i];
}
};
이제 멤버 초기화 목록을 사용하여 AC를 얻는 데 걸리는 시간은 약 108ms 입니다. 이 간단한 예제를 통해 멤버 초기화 목록이 더 효율적임을 알 수 있습니다. . 모든 측정은 LC의 실행 시간에서 이루어집니다.
class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) : preSum(nums.size()+1, 0) {
int ps = 0;
for (int i = 0; i < nums.size(); i++)
{
ps += nums[i];
preSum[i+1] = ps;
}
}
int sumRange(int i, int j) {
return preSum[j+1] - preSum[i];
}
};
통사론:
class Sample
{
public:
int Sam_x;
int Sam_y;
Sample(): Sam_x(1), Sam_y(2) /* Classname: Initialization List */
{
// Constructor body
}
};
초기화 필요 목록 :
class Sample
{
public:
int Sam_x;
int Sam_y;
Sample() */* Object and variables are created - i.e.:declaration of variables */*
{ // Constructor body starts
Sam_x = 1; */* Defining a value to the variable */*
Sam_y = 2;
} // Constructor body ends
};
위 프로그램에서 클래스의 생성자가 실행될 때 Sam_x 와 Sam_y 가 생성됩니다. 그런 다음 생성자 본문에서 해당 멤버 데이터 변수가 정의됩니다.
사용 사례:
C에서는 변수 를 작성하는 동안 정의 해야합니다 . C ++에서와 마찬가지로 초기화 목록을 사용하여 객체를 만드는 동안 Const 및 Reference 변수를 초기화해야합니다. 객체 생성 후 (생성자 본문 내부) 초기화를 수행하면 컴파일 시간 오류가 발생합니다.
기본 생성자가없는 Sample1 (기본) 클래스의 멤버 객체
class Sample1
{
int i;
public:
Sample1 (int temp)
{
i = temp;
}
};
// Class Sample2 contains object of Sample1
class Sample2
{
Sample1 a;
public:
Sample2 (int x): a(x) /* Initializer list must be used */
{
}
};
내부적으로 파생 클래스 생성자를 호출하고 기본 클래스 생성자를 호출하는 파생 클래스에 대한 객체를 만드는 동안 (기본값). 기본 클래스에 기본 생성자가 없으면 컴파일 시간 오류가 발생합니다. 피하기 위해, 우리는
1. Default constructor of Sample1 class
2. Initialization list in Sample2 class which will call the parametric constructor of Sample1 class (as per above program)
클래스 생성자의 매개 변수 이름과 클래스의 데이터 멤버는 동일합니다.
class Sample3 {
int i; /* Member variable name : i */
public:
Sample3 (int i) /* Local variable name : i */
{
i = i;
print(i); /* Local variable: Prints the correct value which we passed in constructor */
}
int getI() const
{
print(i); /*global variable: Garbage value is assigned to i. the expected value should be which we passed in constructor*/
return i;
}
};
우리 모두 알다시피, 두 변수의 이름이 같은 경우 우선 순위가 높은 로컬 변수가 전역 변수입니다. 이 경우 프로그램은 "i"값 {왼쪽 및 오른쪽 변수를 모두 고려합니다. 즉, Sample3 () 생성자 및 로컬 멤버 변수 (i)의 로컬 변수 인 i = i}가 재정의되었습니다. 피하기 위해 우리는
1. Initialization list
2. this operator.
C ++ 핵심 지침 C.49 : 생성자에 할당하는 것이 좋습니다. 기본 생성자에 대한 불필요한 호출을 방지합니다.