생성자

생성자 (Constructors)

객체를 만들 때 그 클래스 타입의 객체를 초기화하는 역할을 하는 게 생성자(constructor) 예요. 생성자는 특별한 선언자 구문으로 선언되는 비정적 멤버 함수예요. 이 페이지에서 생성자의 구문, 멤버 초기화 목록, 위임 생성자 같은 핵심 내용을 살펴볼게요.

출처: cppreference

본문

구문 (Syntax)

생성자는 다음 형태의 멤버 함수 선언자로 선언해요.

class-name ( parameter-list (optional) ) except (optional) attr (optional)
  • class-name — 식별자 표현식. 선택적으로 속성 목록이 뒤따르고, (since C++11) 선택적으로 괄호로 둘러싸일 수 있음.
  • parameter-list — 매개변수 목록.
  • except — 동적 예외 지정(until C++11), 동적 예외 지정 또는 noexcept 지정(since C++11) (until C++17), noexcept 지정(since C++17).
  • attr — (since C++11) 속성 목록.

생성자 선언의 선언 지정자에서 허용되는 지정자는 friend, inline, constexpr(since C++11), consteval(since C++20), explicit뿐이에요(특히 반환 타입은 허용되지 않아요). cv-한정자와 ref-한정자도 허용되지 않아요. 주의할 점은, 생성 중인 객체의 constvolatile 의미는 가장 파생된(most-derived) 생성자가 완료된 후에만 적용되기 때문이에요.

class-name의 식별자 표현식은 다음 형태 중 하나여야 해요.

  • friend 선언에서, 식별자 표현식은 생성자를 이름 붙이는 한정 식별자야 해요.
  • 그렇지 않고, 클래스나 클래스 템플릿의 멤버 지정에 속하는 멤버 선언에서:
    • 클래스에 대해, 식별자 표현식은 바로 둘러싸는 클래스의 주입된 클래스 이름(injected-class-name)이에요.
    • 클래스 템플릿에 대해, 식별자 표현식은 현재 인스턴스화(until C++20)를 이름 붙이는 클래스 이름 또는 바로 둘러싸는 클래스 템플릿의 주입된 클래스 이름(since C++20)이에요.
  • 그렇지 않으면, 식별자 표현식은 끝(unqualified) 식별자가 그 조회 문맥의 주입된 클래스 이름인 한정 식별자예요.

멤버 초기화 목록 (Member initializer list)

어떤 생성자의 함수 정의 본문에서, 복합문의 여는 중괄호 앞에 멤버 초기화 목록(member initializer list) 이 올 수 있어요. 그 구문은 콜론 문자 : 뒤에 쉼표로 구분된 하나 이상의 member-initializer 목록이 따르는 형태예요. 각 member-initializer는 다음 구문을 가져요.

class-or-identifier ( expression-list (optional) )     (1)
class-or-identifier braced-init-list                   (2)  (since C++11)
parameter-pack ...                                     (3)  (since C++11)
  • class-or-identifier — 비정적 데이터 멤버를 이름 붙이는 식별자 또는 클래스 자신(위임 생성자의 경우)이나 직접·가상 베이스를 이름 붙이는 타입 이름.
  • expression-list — 베이스나 멤버의 생성자에 전달할, 비어 있을 수 있는 쉼표로 구분된 인자 목록.
  • braced-init-list — 중괄호로 둘러싼 초기화 목록.
  • parameter-pack — 가변 인자 템플릿 매개변수 팩의 이름.
struct S
{
    int n;
    
    S(int);       // constructor declaration
    
    S() : n(7) {} // constructor definition:
                  // ": n(7)" is the initializer list
                  // ": n(7) {}" is the function body
};

S::S(int x) : n{x} {} // constructor definition: ": n{x}" is the initializer list

int main()
{
    S s;      // calls S::S()
    S s2(10); // calls S::S(int)
}

설명 (Explanation)

생성자는 이름이 없어서 직접 호출할 수 없어요. 생성자는 초기화가 일어날 때 호출되고, 초기화 규칙에 따라 선택돼요. explicit 지정자가 없는 생성자는 변환 생성자(converting constructor)예요. constexpr 지정자를 가진 생성자는 그 타입을 리터럴 타입으로 만들어요. 인자 없이 호출될 수 있는 생성자는 기본 생성자(default constructor)예요. 같은 타입의 다른 객체를 인자로 받는 생성자는 복사 생성자(copy constructor)와 이동 생성자(move constructor)예요.

생성자의 함수 본문을 이루는 복합문이 실행을 시작하기 전에, 모든 직접 베이스, 가상 베이스, 비정적 데이터 멤버의 초기화가 끝나 있어요. 멤버 초기화 목록은 이 하위 객체들의 비-기본 초기화를 지정할 수 있는 자리예요. 기본 초기화할 수 없는 베이스와, 기본 초기화나 기본 멤버 초기화(있다면)(since C++11)로 초기화할 수 없는 비정적 데이터 멤버(예: 참조 타입이나 const-한정 타입의 멤버)에 대해서는 멤버 초기화식을 반드시 지정해야 해요. (클래스 템플릿 인스턴스화의 비정적 데이터 멤버에 대한 기본 멤버 초기화는 멤버 타입이나 초기화식이 의존적이면 유효하지 않을 수 있어요.)(since C++11) 멤버 초기화식이나 기본 멤버 초기화(since C++11)가 없는 익명 공용체나 variant 멤버에 대해서는 초기화가 수행되지 않아요.

class-or-identifier가 가상 베이스 클래스를 이름 붙이는 초기화식들은, 그 객체가 만들어지고 있는 객체의 가장 파생된 클래스가 아닌 클래스의 생성 중에는 무시돼요.

expression-listbraced-init-list에 나타나는 이름들은 생성자의 스코프에서 평가돼요.

class X
{
    int a, b, i, j;
public:
    const int& r;
    X(int i)
      : r(a) // initializes X::r to refer to X::a
      , b{i} // initializes X::b to the value of the parameter i
      , i(i) // initializes X::i to the value of the parameter i
      , j(this->i) // initializes X::j to the value of X::i
    {}
};

멤버 초기화식에서 던져진 예외는 함수 try 블록으로 처리할 수 있어요.

비정적 데이터 멤버가 기본 멤버 초기화를 가지면서 멤버 초기화 목록에도 나타나면, 멤버 초기화식이 사용되고 기본 멤버 초기화는 무시돼요.

struct S
{
    int n = 42;   // default member initializer
    S() : n(7) {} // will set n to 7, not 42
};

(since C++11)

참조 멤버는 멤버 초기화 목록에서 임시 객체에 바인딩될 수 없어요.

struct A
{
    A() : v(42) {} // Error
    const int& v;
};

참고: 기본 멤버 초기화에도 같게 적용돼요.

생성과 파괴 중의 연산 (Operations during construction and destruction)

멤버 함수(가상 멤버 함수 포함)는 생성 중이거나 파괴 중인 객체에 대해 호출할 수 있어요. 마찬가지로 생성 중이거나 파괴 중인 객체는 typeiddynamic_cast의 피연산자가 될 수 있어요.

하지만 이런 연산이 다음 평가 중 어떤 것에서 수행되면 동작은 정의되지 않아요.

  • 생성자의 전제 단언(precondition assertion)의 평가
  • 소멸자의 후제 단언(postcondition assertion)의 평가 (since C++26)
  • 베이스 클래스에 대한 모든 member-initializer가 완료되기 전의 멤버 초기화 목록의 평가

위임 생성자 (Delegating constructor)

클래스 자신의 이름이 멤버 초기화 목록에서 class-or-identifier로 나타나면, 그 목록은 그 하나의 멤버 초기화식으로만 구성되어야 해요. 그런 생성자를 위임 생성자(delegating constructor) 라고 하고, 초기화 목록의 유일한 멤버가 선택한 생성자를 대상 생성자(target constructor) 라고 해요.

이 경우 대상 생성자는 오버로드 해석으로 선택되어 먼저 실행되고, 그 다음 제어가 위임 생성자로 돌아와 그 본문이 실행돼요.

위임 생성자는 재귀적일 수 없어요.

class Foo
{
public: 
    Foo(char x, int y) {}
    Foo(int y) : Foo('a', y) {} // Foo(int) delegates to Foo(char, int)
};

생성자 상속 (Inheriting constructors)

using 선언을 참고하세요. (since C++11)

초기화 순서 (Initialization order)

목록에서 멤버 초기화식의 순서는 무관해요. 실제 초기화 순서는 다음과 같아요.

  1. 생성자가 가장 파생된 클래스에 대한 것이라면, 가상 베이스는 베이스 클래스 선언의 깊이 우선·왼쪽에서 오른쪽 그래프 순회에서 나타나는 순서대로 초기화돼요(왼쪽에서 오른쪽은 base-specifier 목록에서의 나타남을 뜻해요).
  2. 그런 다음 직접 베이스가 이 클래스의 base-specifier 목록에 나타나는 왼쪽에서 오른쪽 순서로 초기화돼요.
  3. 그런 다음 비정적 데이터 멤버가 클래스 정의에서의 선언 순서대로 초기화돼요.
  4. 마지막으로 생성자 본문이 실행돼요.

참고: 만약 초기화 순서가 서로 다른 생성자의 멤버 초기화 목록에 나타나는 순서에 의해 정해졌다면, 소멸자가 파괴 순서가 생성 순서의 역순임을 보장할 수 없었을 거예요.

참고 (Notes)

피처 테스트 매크로 (Feature-test macro)

매크로 표준 피처
__cpp_delegating_constructors 200604L (C++11) 위임 생성자

예제 (Example)

#include <fstream>
#include <string>
#include <mutex>

struct Base
{
    int n;
};   

struct Class : public Base
{
    unsigned char x;
    unsigned char y;
    std::mutex m;
    std::lock_guard<std::mutex> lg;
    std::fstream f;
    std::string s;
    
    Class(int x) : Base{123}, // initialize base class
        x(x),     // x (member) is initialized with x (parameter)
        y{0},     // y initialized to 0
        f{"test.cc", std::ios::app}, // this takes place after m and lg are initialized
        s(__func__), // __func__ is available because init-list is a part of constructor
        lg(m),    // lg uses m, which is already initialized
        m{}       // m is initialized before lg even though it appears last here
    {}            // empty compound statement
    
    Class(double a) : y(a + 1),
        x(y), // x will be initialized before y, its value here is indeterminate
        lg(m)
    {} // base class initializer does not appear in the list, it is
       // default-initialized (not the same as if Base() were used, which is value-init)
    
    Class()
    try // function try block begins before the function body, which includes init list
      : Class(0.0) // delegate constructor
    {
        // ...
    }
    catch (...)
    {
        // exception occurred on initialization
    }
};

int main()
{
    Class c;
    Class c1(1);
    Class c2(0.1);
}

결함 보고 (Defect reports)

다음 동작 변경 결함 보고는 이전에 발표된 C++ 표준에 소급 적용됐어요.

DR 적용 대상 발표된 동작 올바른 동작
CWG 194 C++98 생성자의 선언자 구문이 기껏해야 하나의 함수 지정자만 허용했음(예: 생성자를 inline explicit으로 선언할 수 없었음) 여러 함수 지정자 허용
CWG 257 C++98 추상 클래스가 가상 베이스 클래스에 멤버 초기화식을 제공해야 하는지 불특정이었음 요구되지 않음으로 지정, 그런 멤버 초기화식은 실행 중 무시됨
CWG 263 C++98 생성자의 선언자 구문이 생성자가 friend가 되는 것을 금지했음 생성자가 friend가 될 수 있게 함
CWG 1345 C++98 기본 멤버 초기화가 없는 익명 공용체 멤버가 기본 초기화됨 초기화되지 않음
CWG 1435 C++98 생성자의 선언자 구문에서 "클래스 이름"의 의미가 불명확했음 구문을 전문화된 함수 선언자 구문으로 변경
CWG 1696 C++98 참조 멤버가 임시 객체(수명이 생성자 끝에 끝나는)로 초기화될 수 있었음 그런 초기화는 ill-formed

참고 문헌 (References)

  • C++23 표준 (ISO/IEC 14882:2024): 11.4.5 Constructors [class.ctor], 11.9.3 Initializing bases and members [class.base.init]
  • C++20 표준 (ISO/IEC 14882:2020): 11.4.4 Constructors [class.ctor], 11.10.2 Initializing bases and members [class.base.init]
  • C++17 표준 (ISO/IEC 14882:2017): 15.1 Constructors [class.ctor], 15.6.2 Initializing bases and members [class.base.init]
  • C++14 표준 (ISO/IEC 14882:2014): 12.1 Constructors [class.ctor], 12.6.2 Initializing bases and members [class.base.init]
  • C++11 표준 (ISO/IEC 14882:2011): 12.1 Constructors [class.ctor], 12.6.2 Initializing bases and members [class.base.init]
  • C++98 표준 (ISO/IEC 14882:1998): 12.1 Constructors [class.ctor], 12.6.2 Initializing bases and members [class.base.init]

더 알아보기