본문 바로가기
기술자료/C C++

const에 애하여............

by 알 수 없는 사용자 2009. 8. 11.
728x90
반응형



const 키워드로 선언된 변수

const  int 
num  =  1234;
int  const  num  =  1234;

const 키워드는 해당 변수가 상수임을 의미한다 . 위의 모양 처럼 선언해 놓고
어디선가  num = 0 과 같은 식으로 num 값을 변경시키려고 하면 컴파일러는
왜 상수를 건드리냐면서 에러를 내 뿜을 것이다. C 에서는 const 말고도 전처리기
매크로 #define 을 사용하여 상수를 정의할 수도 있지만 다음과 같은 문제가 발생
할 수 있으며 이러한 버그는 쉽게 발견하기가 힘들기 때문데 const를 권장 한다

// #define으로 정의된 상수의 문제점 
#define num 1 + 2
void fnc( void )
{
  printf( "%d", num * num );
}


// 상수 배열 
const int array[5= { 1234,5 };

배열을 상수로 사용할 수 있다는 것은 곧 const로 선언된 변수를 포인터로
다룰 수 있다는 이야기가 된다.
여기서 생각해 볼 문제는 상수를 가릴키는 포인터 변수가 있을 수 있고 그
자체로 상수인 포인터로 변수가 있을 수 있다. 물론 상수를 가리키면서 그
자체가 상수인 포인터 변수도 선언할 수 있다.

// 상수의 포인터 , 상수 포인터 , 상수의 상수 포인터
const int *p = array
int *const p = array;
const int *const p = array;

위를 보면 약간 복잡해 보일 수도 있는데, 해석하는 방법은 간단하다.
const는 해당 키워드가 위치한 다음에 이어지는 녀석이 상수임을 나타낸다.

첫 번째 문구(상수 포인터) const int *p는 int const *p와 같은 의미이며
이는 *p가 상수라는 것을 의미한다. "C에서는 사용법대로 선언한다.:
를 되새겨 보면 *p는 포인터가 가리키는 int를 말하는 것이다.
즉, 포인터가 상수를 가리키고 있음을 의미한다.

두번째 문장에서는 const 뒤에 p이다. p는 포인터 변수이므로 포인터 변수
자체가 상수값임을 의미한다.

세번째 문장은 *p, p모두 상수임을 알 수 잇다. 풀어 말하면 p는 포인터
변수로서 상수값을 가리키며 또한 p자신도 상수라는 것이다.

// 쓸데없는 예제 풀어보는 시간 
// 컴파일 에러가 발생하지 않는 구문은?
const int *const *const **const *const p;
= 0;
*p = 0;
**p = 0;
***p = 0;
****p = 0;
*****p = 0;

답은 **p = 0이다. const가 붙어있지 않은 *를 찾으면 간단하게 풀린다


C++에서는 변수외에 함수에도 const가 쓰인다 바로 클래스의 멤버 함수 뒤에
붙어서 해당 멤버 함수가 클래스의 멤버 변수들을 건드리지 않는 다는 것을
나타내는데 쓰인다.
// const 멤버 함수 
class Storage
{
  private:
    int m;

  public:
    int GetValue( void ) const{
      return m;
    }
    void SetValue( int v ){
      m = v;
    }
    /* COMPILE ERROR

    void MyMemberFunction( void ) const{
      m = 500;
    }
    */
  
};

위에서 보면 MyMemberFunction이 const로 선언되어 잇음에도 불구하고
클래스의 멤버변수인 m을 건드리는 것을 알 수 있다. 물론 컴파일러는
여기에 태클을 걸며 const 멤버 함수가 왜 멤버 변수를 건드리냐며
ㅈ ㄹ 을 해댈 것이다.

const 멤버 함수는 멤버 변수를 건드리지 않는다는 것을 의미하므로 함수
내부에 지역 변수를 선언 해서 사용하는 것은 허용된다.

그러나, const로 선언되었음에도 불구하고 멤버 변수를 건드릴 필요성이 있는
경우가 발생할 수도 있다. 이런 경우에는 const멤버 함수 여부에 상관없이
건드리고자 하는 변수에 mutable키워드를 붙여주면 된다.

그럼 왜 이렇게 선언해서 사용하느냐? 바로 클래스가 const로 선언된 경우를
대비하기 위함이다.
const로 선언되었거나 const로 인자를 넘겨받은 경우에는 const멤버 함수 밖에
사용할 수 없게 된다.

// const 멤버 함수 2..
void MyFunstion( const storage a)
{
  a.SetValue( 1);    // COMPILE ERROR
  cout << a.GetValue();
}

int main()
{
  Storage a;
  MyFunstion( a );

  return 0;
}



const 멤버 함누 외에도 const멤버 변수도 있을 수 있다. const 멤버 변수는
클래스 생성자에서 클래스(:)초기화를 하여야 한다.
  
// const 멤버 변수와 초기화 방법
class Storage
{
  public:
    Storage( void ) : id(5){
        ;
      }
      /*
      . . . . 
      */

    const int id;
};

뭐하어 번거롭게 이런걸 쓰냐고 생각이 들지도 모르겠다.
실제로 const 키워드는 상수여야 하는 값을 실수로 건들이게 되어
발생하는 논리적 오류를 미연에 방지하고자 도입한 것이다.
그러나 C++에 들어 오면서 컴파일러에게 이변수(또는 인자)가 상수임을
알려 줌으로써 보다 효율적인 프로그램을 할 수 있는 테크닉이 존재 한다.



다음과 같이 위의 Storage 클래스가 템플릿으로 선언된 경우를 생각해보자
// 템플릿으로 선언된 Storage 클래스 
template < class T >
class Storage
{
  private:
    T m;
  public:
    // ....
    void SetValue( T v ){
      m = v;
    }
    // .....
};

위 클래스의 문제점은 무엇일까?
SetValue를 호출할 때에 복사되서 넘어온 값을 또다시 m 으로 복사해
넣어야 한다는 점이다

가령 T가 수만 byte의 char 배열을 동적으로 할당하여 사용하는 클래스이고
이 클래스의 복사 생성자와 대입 연산자가 각각 다시 그 큰 배열을 동적으로
할당하는 동작을 수행하게 된다면, 위 Storage 클래스의 SetValue()를
호출했을때 별다른 처리가 필요한 것도 아닌데 쓸데없이 2번이나 동적 할당을
시도하게 되는 낭비가 존재하게된다.

그럼 이 문제를 어떻게 해결해야 할까? 아래와 같이 SetValue()에서 인자를
reference로 넘겨서 복사 생성자를 호출하지 않도록 하면 될까?

// reference...
template < class T >
class Storage
{
  private:
    T m;
  //.....
  void SetValue( const T & v ){
    m = v;
  }
};

언뜻 보면 해결된 것처럼 보이지만, 여기에도 문제점이 있다. Storage<int>로
선언한 다음 상수 리터럴( 예: s.SetValue(5);)를 넘기려고 하면 또다시 에러가
발생할 것이다 상수 리터럴은 레퍼런스로도 넘길수가 없기 때문이다.

이 때에 사용해야 하는 것이 바로 const 키워드이다 아래와 같이 선언하여
사용하게 되면, 위의 두 문제가 해결 될 것이다.

// const와 reference의 조합
template < class T >
class Storage
{
  // .....
  void SetValue( const T & v){
          m = v;
       }
 
};

// .............

728x90