생성자 (C++)
생성자는 해당 클래스의 인스턴스를 초기화하는 특수 멤버 함수입니다. 생성자를 호출하려면 중괄호 또는 괄호로 묶은 매개 변수를 클래스 이름과 함께 사용합니다.
class Box {
public:
Box(int width, int length, int height){
m_width = width;
m_length = length;
m_height = height;
}
private:
int m_width;
int m_length;
int m_height;
};
class SomeClass{
public:
static void set_box(const Box& aBox) {}
};
int main(){
Box box1(1, 2, 3);
Box box2{ 2, 3, 4 };
SomeClass::set_box(Box{ 5, 6, 7 });
}
자세한 내용은 생성자를 선언하기 위한 규칙을 참조하십시오. 초기화에 대한 자세한 내용은 초기화를 참조하십시오.
생성 순서
생성자는 다음과 같은 순서로 작업을 수행합니다.
생성자는 선언 순서대로 기본 클래스 및 멤버 생성자를 호출합니다.
클래스가 가상 기본 클래스에서 파생된 경우 해당 클래스는 개체의 가상 기본 포인터를 초기화합니다.
클래스가 가상 함수를 포함하거나 상속하는 경우 해당 클래스는 개체의 가상 함수 포인터를 초기화합니다. 가상 함수 포인터는 클래스의 가상 함수 테이블을 가리켜서 가상 함수 호출의 올바른 바인딩이 코딩되도록 합니다.
이러한 포인터는 함수 본문의 모든 코드를 실행합니다.
다음 예제에서는 파생 클래스의 생성자에서 기본 클래스 및 멤버 생성자가 호출되는 순서를 보여 줍니다. 먼저 기본 생성자를 호출하고, 기본 클래스 멤버를 클래스 선언에 표시되는 순서대로 초기화한 다음 파생된 생성자를 호출합니다.
#include <iostream>
using namespace std;
class Contained1 {
public:
Contained1() {
cout << "Contained1 constructor." << endl;
}
};
class Contained2 {
public:
Contained2() {
cout << "Contained2 constructor." << endl;
}
};
class Contained3 {
public:
Contained3() {
cout << "Contained3 constructor." << endl;
}
};
class BaseContainer {
public:
BaseContainer() {
cout << "BaseContainer constructor." << endl;
}
private:
Contained1 c1;
Contained2 c2;
};
class DerivedContainer : public BaseContainer {
public:
DerivedContainer() : BaseContainer() {
cout << "DerivedContainer constructor." << endl;
}
private:
Contained3 c3;
};
int main() {
DerivedContainer dc;
int x = 3;
}
출력은 다음과 같습니다.
Contained1 constructor.
Contained2 constructor.
BaseContainer constructor.
Contained3 constructor.
DerivedContainer constructor.
생성자가 예외를 throw하는 경우 소멸 순서는 생성의 역순입니다.
생성자 함수 본문의 코드가 해제됩니다.
기본 클래스 및 멤버 개체가 선언의 역순으로 소멸됩니다.
생성자가 비대리자인 경우 제대로 생성된 기본 클래스 개체 및 멤버가 모두 소멸됩니다. 하지만 개체가 완전히 생성되지 않으므로 소멸자는 실행되지 않습니다.
명시적 생성자
생성자에 매개 변수가 하나만 있거나 하나를 제외한 모든 매개 변수가 기본값을 갖는 경우 생성자의 explicit 키워드를 사용하여 암시적 형식 변환을 방지할 수 있습니다. 자세한 내용은 생성자 (C++)을 참조하십시오.
기본 생성자
기본 생성자 즉, 매개 변수를 사용하지 않는 생성자는 약간 다른 규칙을 따릅니다.
클래스에서 생성자가 선언되지 않는 경우 컴파일러는 다음과 같이 기본 생성자를 제공합니다.
class Box {
int m_width;
int m_length;
int m_height;
};
int main(){
Box box1{};
Box box2;
}
기본 생성자를 호출하고 괄호를 사용하면 경고가 표시됩니다.
class myclass{};
int main(){
myclass mc(); // warning C4930: prototyped function not called (was a variable definition intended?)
}
이는 가장 까다로운 구문 분석 문제의 한 예입니다. 예제 식을 함수 선언 또는 기본 생성자 호출로 해석할 수 있고 C++ 파서에서는 선언을 다른 항목보다 우선하므로 식이 함수 선언으로 처리됩니다. 자세한 내용은 가장 까다로운 구문 분석(영문)을 참조하세요.
기본값이 아닌 생성자가 선언된 경우 컴파일러는 기본 생성자를 제공하지 않습니다.
class Box {
public:
Box(int width, int length, int height){
m_width = width;
m_length = length;
m_height = height;
}
private:
int m_width;
int m_length;
int m_height;
};
int main(){
Box box1(1, 2, 3);
Box box2{ 2, 3, 4 };
Box box4; // compiler error C2512: no appropriate default constructor available
}
클래스에 기본 생성자가 없는 경우 대괄호 구문만 사용하여 해당 클래스의 개체 배열을 생성할 수 없습니다. 예를 들어 이전 코드 블록에서 다음과 같이 상자 배열을 선언할 수 없습니다.
Box boxes[3]; // compiler error C2512: no appropriate default constructor available
하지만 다음과 같이 이니셜라이저 목록 집합을 사용하여 상자 배열을 초기화할 수 있습니다.
Box boxes[3]{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
복사 및 이동 생성자
복사 생성자는 개체에 대한 참조를 사용하여 개체를 복사합니다. 클래스에 대한 복사 생성자를 제공하지 않는 경우 복사 작업, 이동 작업 또는 소멸자가 선언되어 있더라도 컴파일러는 기본 생성자를 만듭니다. 자세한 내용은 생성자를 선언하기 위한 규칙을 참조하십시오.
이동 생성자를 사용하면 한 개체에서 할당된 메모리를 다른 개체로 전송할 수 있습니다. 자세한 내용은 방법: 이동 생성자 작성을 참조하십시오.
명시적 기본값으로 설정된 생성자 및 삭제된 생성자
복사 생성자, 기본 생성자, 복사 대입 연산자 및 소멸자를 명시적으로 기본값으로 설정할 수 있지만, 이동 생성자 및 이동 대입 연산자의 명시적 기본값은 지원되지 않습니다. 이는 현재 Visual Studio 2015에서 지원되고 있습니다. 모든 특수 함수를 명시적으로 삭제할 수 있습니다. 자세한 내용은 명시적으로 기본 설정 및 삭제된 함수을 참조하십시오.
파생 클래스의 생성자
파생 클래스 생성자는 항상 기본 클래스 생성자를 호출하므로, 완전히 생성된 기본 클래스를 통해서만 다른 작업을 수행할 수 있습니다. 기본 클래스 생성자는 파생 순서대로 호출됩니다. 예를 들어 ClassA는 ClassB에서 파생되고, ClassB는 ClassC에서 파생될 경우 ClassC 생성자, ClassB 생성자, ClassA 생성자의 순으로 호출됩니다.
기본 클래스에 기본 생성자가 없는 경우 파생 클래스 생성자에 기본 클래스 생성자 매개 변수를 제공해야 합니다.
class Box {
public:
Box(int width, int length, int height){
m_width = width;
m_length = length;
m_height = height;
}
private:
int m_width;
int m_length;
int m_height;
};
class StorageBox : public Box {
public:
StorageBox(int width, int length, int height, const string label&) : Box(width, length, height){
m_label = label;
}
private:
string m_label;
};
int main(){
const string aLabel = "aLabel";
StorageBox sb(1, 2, 3, aLabel);
}
다중 상속을 포함하는 클래스에 대한 생성자
클래스가 여러 기본 클래스에서 파생되는 경우 기본 클래스 생성자는 파생 클래스 선언에 나열된 순서대로 호출됩니다.
#include <iostream>
using namespace std;
class BaseClass1 {
public:
BaseClass1() {
cout << "BaseClass1 constructor." << endl;
}
};
class BaseClass2 {
public:
BaseClass2() {
cout << "BaseClass2 constructor." << endl;
}
};
class BaseClass3{
public:
BaseClass3() {
cout << "BaseClass3 constructor." << endl;
}
};
class DerivedClass : public BaseClass1, public BaseClass2, public BaseClass3 {
public:
DerivedClass() {
cout << "DerivedClass constructor." << endl;
}
};
int main() {
DerivedClass dc;
}
다음과 같이 출력됩니다.
BaseClass1 constructor.
BaseClass2 constructor.
BaseClass3 constructor.
DerivedClass constructor.
생성자의 가상 함수
생성자에서 가상 함수를 호출할 경우 주의하시기 바랍니다. 기본 클래스 생성자가 파생 클래스 생성자보다 항상 먼저 호출되므로, 기본 생성자에서 호출되는 함수는 파생 클래스 버전이 아닌 기본 클래스 버전입니다. 다음 예제에서 DerivedClass를 생성하면 BaseClass 생성자에 의해 **print_it()**의 DerivedClass 구현이 실행되기 이전에 DerivedClass의 print_it() 구현이 실행됩니다.
#include <iostream>
using namespace std;
class BaseClass{
public:
BaseClass(){
print_it();
}
virtual void print_it() {
cout << "BaseClass print_it" << endl;
}
};
class DerivedClass : public BaseClass {
public:
DerivedClass() {
print_it();
}
virtual void print_it(){
cout << "Derived Class print_it" << endl;
}
};
int main() {
DerivedClass dc;
}
출력은 다음과 같습니다.
BaseClass print_it
Derived Class print_it
생성자 및 복합 클래스
클래스 형식 멤버를 포함하는 클래스를 복합 클래스라고 합니다. 복합 클래스의 클래스 형식 멤버를 만들 때 생성자가 클래스의 자체 생성자보다 먼저 호출됩니다. 포함된 클래스에 기본 생성자가 없는 경우 복합 클래스의 생성자에서 초기화 목록을 사용해야 합니다. 이전 StorageBox 예제에서 m_label 멤버 변수의 형식을 새 Label 클래스로 변경할 경우 기본 클래스 생성자를 호출하고 m_label 생성자에서 StorageBox 변수를 초기화해야 합니다.
class Label {
public:
Label(const string& name, const string& address) { m_name = name; m_address = address; }
string m_name;
string m_address;
};
class StorageBox : public Box {
public:
StorageBox(int width, int length, int height, Label label)
: Box(width, length, height), m_label(label){}
private:
Label m_label;
};
int main(){
// passing a named Label
Label label1{ "some_name", "some_address" };
StorageBox sb1(1, 2, 3, label1);
// passing a temporary label
StorageBox sb2(3, 4, 5, Label{ "another name", "another address" });
// passing a temporary label as an initializer list
StorageBox sb3(1, 2, 3, {"myname", "myaddress"});
}
위임 생성자
위임 생성자는 동일한 클래스의 다른 생성자를 호출하여 초기화 작업의 일부를 수행합니다. 다음 예제에서는 파생 클래스에 세 개의 생성자가 있는데 두 번째 생성자는 첫 번째 생성자에 위임되고, 세 번째 생성자는 두 번째 생성자에 위임됩니다.
#include <iostream>
using namespace std;
class ConstructorDestructor {
public:
ConstructorDestructor() {
cout << "ConstructorDestructor default constructor." << endl;
}
ConstructorDestructor(int int1) {
cout << "ConstructorDestructor constructor with 1 int." << endl;
}
ConstructorDestructor(int int1, int int2) : ConstructorDestructor(int1) {
cout << "ConstructorDestructor constructor with 2 ints." << endl;
throw exception();
}
ConstructorDestructor(int int1, int int2, int int3) : ConstructorDestructor(int1, int2) {
cout << "ConstructorDestructor constructor with 3 ints." << endl;
}
~ConstructorDestructor() {
cout << "ConstructorDestructor destructor." << endl;
}
};
int main() {
ConstructorDestructor dc(1, 2, 3);
}
출력은 다음과 같습니다.
ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor constructor with 3 ints.
생성자에 의해 만들어지는 개체는 생성자가 완료되는 즉시 완전하게 초기화됩니다. **DerivedContainer(int int1)**는 성공하지만 **DerivedContainer(int int1, int int2)**는 실패하고 소멸자가 호출됩니다.
class ConstructorDestructor {
public:
ConstructorDestructor() {
cout << "ConstructorDestructor default constructor." << endl;
}
ConstructorDestructor(int int1) {
cout << "ConstructorDestructor constructor with 1 int." << endl;
}
ConstructorDestructor(int int1, int int2) : ConstructorDestructor(int1) {
cout << "ConstructorDestructor constructor with 2 ints." << endl;
throw exception();
}
ConstructorDestructor(int int1, int int2, int int3) : ConstructorDestructor(int1, int2) {
cout << "ConstructorDestructor constructor with 3 ints." << endl;
}
~ConstructorDestructor() {
cout << "ConstructorDestructor destructor." << endl;
}
};
int main() {
try {
ConstructorDestructor cd{ 1, 2, 3 };
}
catch (const exception& ex){
}
}
출력:
ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor destructor.
자세한 내용은 균일 초기화 및 생성자 위임을 참조하십시오.