SW 그리고 아빠 엔지니어링 중...

아는 만큼 보이고, 경험해봐야 알 수 있고, 자꾸 써야 내 것이 된다.

L C++

(기초) 생성자(constructors) 초기화

보리남편 김 주부 2024. 4. 17. 02:23

생성자에서의 초기화

이것이 왜 좋은 코드인지 알아보자. 

    class A {   // Good
        string s1;
    public:
        A() : s1{"Hello, "} { }    // GOOD: directly construct
        // ...
    };

 

일전에 대입과 초기화에 대한 내용을 정리했었는데, 초기화와 별도로 대입을 하면 user defined type은 함수가 호출되는 동작을 추가로 하게 된다. 아래코드에 B 클래스는 얼핏 보면 생성자 안에서 바로 s1을 초기화했지만 사실 이것은 초기화를 한 후 대입을 한 것이다.

class B {   // BAD
    string s1;
public:
    B(const char* p) { s1 = p; }   // BAD: default constructor followed by assignment
    // ...
};

 

컴파일러가 위 코드를 아래와 같이 코드를 생성을 한다.

//컴파일러가 생성한 코드
class B
{
  std::basic_string<char> s1;
  
  public: 
  inline B(const char * p)  : s1{std::basic_string<char>()}
  {    this->s1.operator=(p);  }
  
};
s1{std::basic_string<char>()}
B 클래스가 생성되는 시점에 컴파일러가 str::string() 디폴트 생성자를 생성하고, 생성자 안에서는 대입연산자로 대입하여 함수가 두 번 호출되게 된다.

 

컴파일러에서 만들어준 문법처럼 아래와 같이 생성자() 뒤에 ':'을 적고 s1{p} 혹은 s1(p) 형태로 나열하면 이것은 대입이 아니라 초기화가 되기에 좋은 코드이다.

class A {   // Good
    string s1;
public:
    A(string p) : s1{p} { }    //or s1(p)
    // ...
};

 

이렇게 하면 아래코드처럼 p값이 초기화되기 이전에 호출되는 오류를 예방도 할 수 있다.

class C {   // UGLY, aka very bad
    int* p;
public:
    C() { cout << *p; p = new int{10}; }   // accidental use before initialized
    // ...
};

 

📜 그래서 C++핵심 가이드라인에서는 아래와 같이 가이드하고 있다.
C.49: 생성자 안에서의 대입보다는 초기화를 선호하라 (C.49: Prefer initialization to assignment in constructors)

멤버변수가 여러 개 일 때 초기화 순서를 알아보자.

아래코드는 멤버변수가 여러 개를 초기화하고 있다.

Q. m1, m2 값은 어떻게 될까?

class Foo {
    int m1;
    int m2;
public:
    Foo(int x) :m2{x}, m1{++x} { }
};

Foo x(1);

 

m2 = 1, m1 = 2 일까❓
결과는 m2, m1 이 '2'이다. 🤔
왜냐하면 컴파일러가 멤버변수 순서대로 조정시키기 때문에 멤버변수 초기화는 아래와 같이 조정된다. 이렇게 조정된 결과로 m1 = 2, m2 = 2가 된 것이다.
Foo(int x)
  : m1{++x}
  , m2{x}

 

위와 같이 단순한 오류는 쉽게 찾을지 모르지만, 아래와 같은 코드는 의도대로 동작하지 않지만 에러도 발생하지 않아 오동작의 원인을 한참 디버깅해야 할지도 모른다.

struct vector
{
	int* buff;
	int size;
public:
                     (2)   , (1)
vector(int sz) : size(sz), buff(new int[size]) //size 값이 나중에 초기화 된다.
  • 기대한 결과 : size 수 만큼 int 배열 메모리 할당
  • 실제 결과 : int 메모리 할당

 

📜 그래서 C++핵심 가이드라인에서는 아래와 같이 가이드하고 있다.
C.47: 멤버 변수들은 선언된 순서대로 초기화하라  (C.47: Define and initialize data members in the order of member declaration)

 

 

💬 생성자 관련 응용 내용은 부담스러운 길이가 될 것 같아 별도로 작성하겠다.

 

참고


* 본 글에 예제 코드는 C++ Core Guidelines 샘플코드를 활용하였습니다.

 

C++ 핵심 가이드라인 한글화 프로젝트 링크

C++ Core Guidelines 원문 링크

 

 

728x90

'L C++' 카테고리의 다른 글

애증의 const 사용하기 (1)  (0) 2024.05.11
(응용) 생성자(constructors) 초기화  (16) 2024.04.21
std::move the 이해하기  (40) 2024.04.12
perfect forwarding을 perfect하게 이해하기  (14) 2024.03.29
std::string_view의 이해  (0) 2024.03.19