ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective C++] 4. 객체를 사용하기 전에 반드시 객체를 초기화하자
    C_C++ 프로그래밍/Effective C++ 2019. 9. 3. 20:50

          로재의 개발 일기      

    객체를 초기화하자

    C++에서 어떠한 객체를 초기화를 진행했을때

    완벽하게 그것을 보장을 해줄 수도... 아닐 수도 있습니다.


    예를 하나 들어볼까요?

    int x        // 이 변수는 0으로 초기화가 거의 확정이죠.


    하지만 다음과 같을 시에는

    class Point {

    int x,y

    }

    ....

    Point p;            // Point p의 객체의 초기화가 보장되지 못합니다.


    이번 항목에서는 다음과 같이 지시하고 있었습니다.

    1. 기본 제공 타입의 객체는 직접 초기화를 진행한다.

    (되는 경우도 있고 아닌 경우도 있지만 그냥 모두 초기화하자)


    2. 생성자에서 대입을 통한 초기화가 아닌 초기화 리스트를 사용하여 진행하자


    3. 여러 번역 단위에 존재하는 비지역 정적 객체들의 초기화 순서 문제를 피하기 위해서 설계하자

       이는 비지역 정적 객체를 지역 정적 객체로 바꾸어주면 된다.

     


      기본 제공 타입 초기화

    단순 기초인 초기화이지만 여기에서 시작됩니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /*
     * 기본제공 타입의 객체는 직접 손으로 초기화를 하자
     * 경우에 따라서 저절로 초기화가 되기도 하지만
     * 그렇지 않은 경우도 있기 때문이다.
     */
     
    #include <iostream>
     
    int main(){
            int x = 0;      // x init
            const char* str = "String String String";   // str init
            double d;
            std::cin >> d;      // d를 읽어들이면서 초기화 진헹
            return 0
    }
     

    cs


    입력 스트림을 사용을 하거나 직접 손으로 초기화를 진행하라는 부분입니다.

    쉬우니 넘어갈게요.


      초기화 리스트를 사용합시다


    다음과 같은 코드를 보면

    Object x;            // 생성자
    x = 'a';                // 대입 연산자

    일때 x에 a가 들어갑니다.

    즉 두 번 함수가 호출되네요.



    하지만 초기화 리스트를 사용하면


    Obejct x = 'a';        // 한번에 접근이 가능합니다.



    소스코드를 통해서 확인해보겠습니다.

    우선은 초기화를 대입 연산으로 처리한 경우 입니다.



    before.hpp

    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
    45
    46
    47
    48
    #include<iostream>
    #include<list>
    class Score{
            public:
                    Score() {};
                    Score(const int& korean, const int& english, const int& math);
                    void ShowScore();
     
            private:
                    int korean;
                    int english;
                    int math;
    };
     
    Score::Score(const int& theKorean, const int& theEnglish, const int& theMath){
        korean = theKorean;
        english = theEnglish;
        math = theMath;
    }
     
    void Score::ShowScore(){
        std::cout << korean << ' ' << english << ' ' << math << '\n';
    }
     
    class Student{
            public:
                    Student() {};
                    Student(const std::string& name, const std::string& address,
                                    const std::list<Score>& scoreList);
                    void ShowInfo();
            private:
                    std::string name;
                    std::string address;
                    std::list<Score> scoreList;
                    std::size_t id;
    };
     
    Student::Student(const std::string& theName, const std::string& theAddress, const std::list<Score>& theScoreList){
        name = theName;
        address = theAddress;
        scoreList = theScoreList;
        id = 0;
    }
     
    void Student::ShowInfo(){
            std::cout << name << ' ' << address << ' ';
            scoreList.front().ShowScore();
    }
    cs


    After.hpp

    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
    45
    46
    47
    #include<iostream>
    #include<list>
    class Score{
            public:
                    Score() {};
                    Score(const int& korean, const int& english, const int& math);
                    void ShowScore();
     
            private:
                    int korean;
                    int english;
                    int math;
    };
     
    // 초기화 리스트를 사용하여 초기화와 대입을 
    // 한번에 처리하도록 함
    // 생성만 할 경우에는 기본생성자
    // : korean(), english(), math() {} 로 처리가 가능하다.
    Score::Score(const int& theKorean, const int& theEnglish, const int& theMath)
        : korean(theKorean), english(theEnglish), math(theMath) {   }
     
    void Score::ShowScore(){
        std::cout << korean << ' ' << english << ' ' << math << '\n';
    }
     
    class Student{
            public:
                    Student() {};
                    Student(const std::string& name, const std::string& address,
                                    const std::list<Score>& scoreList);
                    void ShowInfo();
            private:
                    std::string name;
                    std::string address;
                    std::list<Score> scoreList;
                    std::size_t id;
    };
     
    // 마찬가지로 사용한 초기화 리스트
    Student::Student(const std::string& theName, const std::string& theAddress, const std::list<Score>& theScoreList)
        : name(theName), address(theAddress), scoreList(theScoreList), id(0)
        {};
     
    void Student::ShowInfo(){
            std::cout << name << ' ' << address << ' ';
            scoreList.front().ShowScore();
    }
    cs

    소스를 비교해보면

    생성자 부분에서 다음과 같이 처리하여 
    : variable(para),     ...  { }

    초기화와 동시에 대입의 효과를 얻을 수 있게 됩니다.



      비지역 정적 객체의 초기화는 피하자

    정젹 객체는 총 다섯개이며
    다음과 같습니다.

    1. 전역 객체

    2. 네임스페이스 유효범위에서 제시한 객체

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

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

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


    이때 만일 정적 객체가 아닌 객체를
    한쪽에서 초기화가 되지 않은 상태로 사용하면 ...
    당연히 안되겠죠..?

    그것을 없애자는 말인데
    C++에서는 이것을 완벽하게 보장을 할 수 있는 기법은 없습니다.



    왜냐면 서로 다른 번역 단위에서 정의된
    정의된 비지역 정적 객체들의 초기화 순서는

    ' 정해져 있지 않거든요 '





    하지만 설계에 약간의 변화를 주면 이 문제는 사전에 아예
    봉쇄가 가능하게 됩니다.

    즉 비지역 정적 객체를 지역 정적 객체로 바꾸는 것입니다.

    이 설명은 위에서 작성한 After.hpp을 사용한
    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
    #include <iostream>
    #include <vector>
    #include <list>
    #include "After.hpp"
     
    int main(){
            std::list<Score> scoreList;
            std::string name, address;
            int korean, english, math;
     
            std::cin >> korean >> english >> math;
     
            Score score(korean, english, math);
            scoreList.push_back(score);
     
            std::cin >> name >> address;
     
            Student student(name, address, scoreList);
            student.ShowInfo();
     
     
            return 0;
    }
     
    cs

    다음과 같은 경우 After.hpp의 요소들이 먼저 초기화된다는 확실한 보장도
    심지어 hpp(헤더파일)을 나누어 작성을 한다면
    이것도 또한 완벽하는 보장도 없습니다.



    해결법은 다음과 같이 제시하고 있습니다.
    비지역 정적 객체를 지역 정적 객체를 바꾸는 함수를 사용하여
    이를 사용하면 초기화가 되지 않았더라도
    객체를 정의하고 초기화를 진행하고
    이 객체의 '참조자'를 반환합니다.


    After2.hpp
    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
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    #include<iostream>
    #include<list>
     
    class Score{
            public:
                    Score() {};
                    Score(const int& korean, const int& english, const int& math);
                    Score& score_sample();
                    void ShowScore();
     
            private:
                    int korean;
                    int english;
                    int math;
    };
     
    // 초기화 리스트를 사용하여 초기화와 대입을 
    // 한번에 처리하도록 함
    // 생성만 할 경우에는 기본생성자
    // : korean(), english(), math() {} 로 처리가 가능하다.
     
    Score::Score(const int& theKorean, const int& theEnglish, const int& theMath)
        : korean(theKorean), english(theEnglish), math(theMath) {   }
     
     
    // 비지역 정적 객체들의 초기화 순서 문제를 피하기 위해
    // 지역 정적 객체로 바꾸어 주는 함수
    // 간단히 말해서 객체를 대신하는 함수
    Score& score_sample(){
            static Score sc;
            return sc;
    }
     
    void Score::ShowScore(){
        std::cout << korean << ' ' << english << ' ' << math << '\n';
    }
     
    class Student{
            public:
                    Student() {};
                    Student(const std::string& name, const std::string& address,
                                    const std::list<Score>& scoreList);
                    Student& student_sample();
                    void ShowInfo();
            private:
                    std::string name;
                    std::string address;
                    std::list<Score> scoreList;
                    std::size_t id;
    };
     
    // 마찬가지로 사용한 초기화 리스트
    Student::Student(const std::string& theName, const std::string& theAddress, const std::list<Score>& theScoreList)
        : name(theName), address(theAddress), scoreList(theScoreList), id(0)
        {}
     
    // student_sample도 넣어본다.
    Student& student_sample(){
            static Student st;
            return st;
    }
     
    void Student::ShowInfo(){
            std::cout << name << ' ' << address << ' ';
            score_sample().ShowScore();
    }
     
    cs




    마무리 및 ...

    불확실성을 피해가자.

    static과 생성자 리스트의 신선함...


    전체적인 소스는 신경쓰지 않았습니다만

    Linux 상에서 g++로 컴파일 및 실행은 충분히 가능하게 작성하였습니다.

    예시로만 봐주세요~


    ※ 본 글은 개인 포트폴리오 혹은 공부용으로 사용하기 때문에, 무단 복사 유포는 금지하지만, 개인 공부 용도로는 얼마든지 사용하셔도 좋습니다



    반응형
Designed by Tistory.