ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective C++] 3. 낌새만 보이면 const를 들이대 보자!
    C_C++ 프로그래밍/Effective C++ 2019. 8. 29. 16:30

          로재의 개발 일기      

    const

    const의 가장 큰 장점은 객체의 외부 변경을 불가능하도록 

    소스코드에 작성을 하면 컴파일러가 이를 반드시 지켜준다는 점입니다.

    즉,  개발자 입장에서 의도치 않은 사용을 금지하거나, 그러한 의도를 소스코드에 보일 수 있다는 것 입니다.


      poiner const


    char name[] = "rojae";                 // 문자열 선언

    char *p = name;                            // 비상수 데이터, 비상수 포인터

    const char* p = name;                // 상수 데이터, 비상수 포인터

    char* const p = name;                // 비상수 데이터, 상수 포인터

    const char* const p = name;     // 상수 데이터, 상수 포인터

    상수라는 단어가 들어가면 외부에서 변경이 불가능하다는 것입니다.

    또한 void f1(const Widget *pw);
    void f1(Widget const *pw)

    두 문장 모두 똑같은 의미를 가지며

    이는 * (포인터)의 위치의 전과 후에 따라서
    상수가 어느 부분에 속하는 알 수 있게 됩니다.


    간단한 예시를 첨부하겠습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #include<iostream>
     
    int main(){
            int b = 9;
            const int *= &b;          // 비상수 포인터, 상수 데이터
                                        // 포인터가 가르치는 대상이 상수
     
            std::cout << "a :" << *a
                    << " b :" << b;
     
            int c = 9;
            int * const d = &c;         // 상수 포인터, 비상수 데이터
                                        // 포인터 자체가 상수   
     
            std::cout << " c :" << c
                        << " d :" << *d;
            //*a = c;                       // 상수 데이터이기 때문에 값 변경 불가
            *= b;
     
            return 0;
    }
     
    cs


      상수 멤버 함수

    const는 멤버함수에도 붙을 수 있는데요.

    " 이는 해당 멤버 함수가 상수 객체에 대해서 호출될 함수이다. " 라는 뜻입니다.

    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
    #include<iostream>
     
    class TextBook{
            public:
                    TextBook(std::string s)
                    {   text.assign(s);         }
     
                    // 상수 멤버 함수를 통해  const로 지정
                    const std::string& operator[](std::size_t position) const
                    {   return text;    }
     
                    // 일반적인 객체 지정
                    std::string& operator[](std::size_t position)
                    {   return text;    }
     
                    void print(){
                        std::cout << text << '\n';
                    }
                    void print() const{
                        std::cout << text << '\n';
                    }
     
            private:
                    std::string text;
    };
    int main(){
              TextBook tb("Hello");
              std::cout << tb[0<< '\n';
     
              const TextBook ctb("Hello");
              std::cout << ctb[0<< '\n';
     
              tb[0= 'J';
              //ctb[0] = 'J';       // const TextBook에 대해서 읽기만 가능하다.
     
              tb.print();
              ctb.print();
              return 0;
      }
     
    cs


    실행시켜보면 const가 붙은 객체는

    const에 따라서만 실행되는 것을 알 수 있습니다.


    또한 이 부분에서

    1
    std::string& operator[](std::size_t position)
    cs

    std::string&을 쓴 이유는 반환되어 값을 정정할 때

    복사된 값이 아닌 참조되는 값을 수정하기 위함입니다.




      그렇다면 상수 멤버 함수에서 변경하고 싶은 데이터는?

    C++의 비기. mutable을 사용하면 됩니다.

    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
    #include<iostream>
     
    class TextBook{
            public:
                    TextBook(std::string s)
                    {   text.assign(s);         }
     
                    // 상수 멤버 함수를 통해  const로 지정
                    const char& operator[](std::size_t position) const
                    {   return text[position];  }
     
                    // 일반적인 객체 지정
                    char& operator[](std::size_t position)
                    {   return text[position];  }
     
                    void print(){
                        std::cout << text << '\n';
                    }
                    void print() const{
                        std::cout << text << '\n';
                    }
     
                    std::size_t length() const;
     
            private:
                    std::string text;
     
                    // mutable로 선언된 데이터들은 상수 멤버 함수안에서도 수정이 가능하다 (어느 순간이든).
                    mutable bool lengthIsValid;
                    mutable std::size_t textlength;
    };
     
    std::size_t TextBook::length() const{
        if(!lengthIsValid){
                textlength = text.size();
                lengthIsValid = true;
        }
        return textlength;
    }
    int main(){
              TextBook tb("Hello");
              std::cout << tb[0<< '\n';
     
              const TextBook ctb("Hello");
              std::cout << ctb[0<< '\n';
     
              tb[0= 'J';
              //ctb[0] = 'J';       // const TextBook에 대해서 읽기만 가능하다.
     
              tb.print();
              std::cout << tb.length() << '\n';
              ctb.print();
              std::cout << ctb.length() << '\n';
              return 0;
      }
    cs


    위의 예제 소스 사이에 private을 보면

    1
    2
    3
     // mutable로 선언된 데이터들은 상수 멤버 함수안에서도 수정이 가능하다 (어느 순간이든).
     mutable bool lengthIsValid;
     mutable std::size_t textlength;
    cs

    이러한 소스가 있고 이 변수들은


    1
    2
    3
    4
    5
    6
    7
    std::size_t TextBook::length() const{
        if(!lengthIsValid){
                textlength = text.size();
                lengthIsValid = true;
        }
        return textlength;
    }
    cs

    멤버함수 내에서 (심지어 상수 멤버 함수 안에서도 조차)

    수정이 가능합니다.


      하지만 상수 멤버 함수와 그냥 멤버 함수와 겹치는 부분은..?

    맞습니다.

    함수가 겹치는 부분이 많아지게 되고

    이는 소스코드 길이가 길어지고 낭비가 되는데요.


    이 부분을 수정해보았습니다.

    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
    #include<iostream>
     
    class TextBook{
            public:
                    TextBook(std::string s)
                    {   text.assign(s);         }
     
                    // 상수 멤버 함수를 통해  const로 지정
                    const char& operator[](std::size_t position) const
                    {
                            // do something..   
                            return text[position];
                    }
     
                    // 일반적인 객체 지정
                    char& operator[](std::size_t position)
                    {
                            // static_cast를 통해서 형 변환 이후
                            // const char& operator[](size_t position) const를 통하여 작업 실행
                            // 이후 const_cast를 통해서 const를 제거한다.
                            return const_cast<char &> ( static_cast<const TextBook&> (*this)[position]);
                    }
     
                    void print(){
                        std::cout << text << '\n';
                    }
                    void print() const{
                        std::cout << text << '\n';
                    }
     
                    std::size_t length() const;
     
            private:
                    std::string text;
     
                    // mutable로 선언된 데이터들은 상수 멤버 함수안에서도 수정이 가능하다 (어느 순간이든).
                    mutable bool lengthIsValid;
                    mutable std::size_t textlength;
    };
     
     
    std::size_t TextBook::length() const{
        if(!lengthIsValid){
                textlength = text.size();
                lengthIsValid = true;
        }
        return textlength;
    }
    int main(){
              TextBook tb("Hello");
              std::cout << tb[0<< '\n';
     
              const TextBook ctb("Hello");
              std::cout << ctb[0<< '\n';
     
              tb[0= 'J';
              //ctb[0] = 'J';       // const TextBook에 대해서 읽기만 가능하다.
     
              tb.print();
              std::cout << tb.length() << '\n';
              ctb.print();
              std::cout << ctb.length() << '\n';
              return 0;
      }
     
    cs


    자 보면 여기가 달라졌네요.


    1
    2
    3
    4
    5
    6
    7
    8
    char& operator[](std::size_t position){
          // static_cast를 통해서 형 변환 이후
          // const char& operator[](size_t position) const를 통하여 작업 실행
          // 이후 const_cast를 통해서 const를 제거한다.}
          
        return const_cast<char &> ( static_cast<const TextBook&> (*this)[position]);
     
    }
    cs


    우선 std::string &과 char&은 제가 임의 상 작성을 하였기 때문에 무시합시다.

        return const_cast<char &> ( static_cast<const TextBook&> (*this)[position]);

    보면 const_cast<char &>로

    static_cast<const TextBook&> (*this)[position]); 의 결과 값을

    캐스팅하여 char &로 변환해줍니다.



    이때 ( static_cast<const TextBook&> (*this)[position])는

    (const TextBook&)형 변환을 한

    (*this) => 현재 객체의 정보가 들어간 포인터입니다.



    즉, 상수 멤버 함수를 통해서 동작을 하지만

    마지막에 const char&가 아닌 char&로 형 변환을 해주는 것을

    알 수 있습니다.






    마무리

    어렵당

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


    반응형
Designed by Tistory.