객체 초기화(effective c++ 04)

2015. 7. 24. 06:35프로그래밍/Effective C++

728x90
728x90
서론

C++의 객체 초기화는 중구난방!

물론 규칙은 있다.

C++의 C 부분은 사용하고 있으며 런타임 비용이 소모될 수 있는 상황이라면 초기화 된다는 보장이 없다.

C가 아닌 부분은 때때로 달라진다.

예시) 배열 vs 백터


모든 것을 해결하는 방법!

모든 객체를 사용하기 전에 항상 초기화 하는 것.


생성자

대입(assignment)과 초기화(initialization)을 구분하자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// ConsoleApplication7.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//
// 작성자 조민혁
 
#include "stdafx.h"
#include <vector>
 
class MyClass
{
public:
    MyClass() : m_member(0) { }
    ~MyClass() { }
    MyClass(const MyClass& rhw) { m_member = rhw.m_member; }
private:
    int m_member;
};
 
class YourClass
{
public:
    YourClass() : m_myString(NULL), m_myClass(0) { }
    ~YourClass();
    YourClass::YourClass(const std::string str, const std::vector<MyClass>);
 
private:
    std::string m_myString;
    std::vector<MyClass> m_myClass;
};
 
YourClass::YourClass(const std::string str, const std::vector<MyClass> cMyClass)
// 이것은 초기화(initialization)
// 복사생성자 호출
    : m_myString(str), m_myClass(cMyClass)
{
    // 이것은 대입(assignment) 
    // 디폴트 생성자 + 대입
//    m_myString = str;
//    m_myClass = cMyClass;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
    return 0;
}
cs


기본 생성자 호출 후에 복사 대입 연산자를 연달아 호출하는 방식은 매우 비효율적이다.

복사 생성자를 한 번 호출하는 쪽이 효율적!


기본 생성자이든 아니든 클래스 데이터 맴버는 모두 초기화 리스트에 항상 올려주는 센스.

상수이거나 참조자일 경우 반드시 초기화 해야한다.


객체를 구성하는 데이터의 초기화 순서

- 기본 클래스는 파생 클래스보다 먼저 초기화 된다.

- 클래스 데이터 멤버는 그들이 선언된 순서대로 초기화된다.

모든 컴파일러를 만록하고 항상 위의 규칙은 똑같다.


별개의 번역단위에서 정의된 비지역 정적 객체들의 초기화 순서는 정해져 있지 않다.

정적 객체(static object)

자신이 생성된 시점부터 프로그램이 끝날 때까지 살아 있는 객체를 일컫는다.

- 전역 객체

- 네임스페이스 유효범위에 정의된 객체

- 클래스 안에서 static으로 선언된 객체

- 함수 안에서 static으로 선언된 객체

- 파일 유효범위에서 static으로 정의된 객체


함수 내부에 정의된 것들을 지역 정적 객체(local static object)라고 하고

나머지는 비지역 정적 객체(non-local static object)라고 한다.


번역 단위(translation unit)

컴파일을 통해 하나의 목적 파일(object file)을 만드는 바탕이 되는 소스 코드


문제점

비지역 정적 객체들의 초기화에 대해 적절한 순서를 결정하기란 어렵다.


해결 방법

비지역 정적 객체를 지역 정적 객체로 바꾼다. 

그것은 싱글톤 패턴!

함수 속에서 해당 객체를 정적 객체로 선언하고 참조자를 이용하여 반환하여 사용한다.

* 주의 - 반드시 참조자로 반환할땐 초기화 된 객체를 반환해야한다.

* 또 주의 - 참조자 반환 함수는 내부적으로 정적 객체를 쓰기 때문에 다중 스레드 시스템에서 동작에 장애가 될 수 있다.

이를 해결하는 방법은 다중스레드로 돌입하기 전에 시동 단계에서 참조자 반환 함수를 전부 호출 하는 것.

이렇게 하면 초기화에 관계된 경쟁 상태(race condition)를 해결할 수 있다.


main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ExternTest
{
public:
    ExternTest() 
    { 
        // 싱글톤 패턴으로 static으로 부르는 방법
        ecCall().externPrint(); 
        // extern을 이용하여 부르는 방법
        ec.externPrint(); 
    }
    ~ExternTest() { }
 
private:
    externClass& ecCall() 
    { 
        static externClass ec2;
        return ec2; 
    }
};
 
int _tmain(int argc, _TCHAR* argv[])
{
    ExternTest test;
    return 0;
}
cs


externClass.c

1
2
3
4
#include "stdafx.h"
#include "externClass.h"
 
externClass ec;
cs


externClass.h

1
2
3
4
5
6
7
8
9
#pragma once
class externClass
{
public:
    externClass(void){ }
    ~externClass(void){ }
    void externPrint() { printf("popura!"); }
};
extern externClass ec;
cs


정리

기본제공 타입의 객체는 직접 초기화.

맴버 초기화 리스트 사용하기. 대입은 NO. 초기화 리스트 작성은 맴버 나열 순서와 동일하게.

여러 번역 단위에 있는 비지역 정적 객체들의 초기화 순서 문제를 피하여 설계한다.

(비지역 정적 객체를 지역 정적 객체로 바꾼다.)




728x90
반응형