본문 바로가기
  • 쓸쓸한 개발자의 공부방
C++

[C++] 상수 멤버 함수 const

by 심찬 2021. 8. 23.

 

 

상수 멤버 함수 const

 

함수 선언 및 구현시 함수 괄호 ( ) 뒤에 const가 붙는 함수

  • void print() const

 - 상수 멤버 함수 안에서는 모든 멤버를 상수 취급한다.

 - 멤버 데이터의 값을 읽을 수는 있지만 변경할 수는 없다.

 - 코드 작성시 안정성

 - 상수 객체는 상수 멤버 함수만 호출할 수 있다.

#include <iostream>

class Point
{
    int x, y;
public:
    Point(int a = 0, int b = 0) : x(a), y(b) {}
    
    void set( int a, int b ) { x = a; y = b; }
    
    void print() const // 상수 멤버 함수
    {
        x = 10; // error : 상수 멤버 함수 안에서 모든 멤버를 상수 취급하기 때문에 변경시 에러!
        std::cout << x << ",  " << y << std::endl;
    }
};

int main()
{
    
}

main.cpp:13:13: error: assignment of member ‘Point::x’ in read-only object

 

 

const를 사용하지 않은 일반 class 와 일반 객체 pt 선언, 값 변경, 함수 호출 등의 코드로

아무 문제 없이 수행된다.

#include <iostream>

class Point
{
public:
    int x, y;
    Point(int a = 0, int b = 0) : x(a), y(b) {}
    
    void set( int a, int b ) { x = a; y = b; }
    
    void print()
    {
        std::cout << x << ",  " << y << std::endl;
    }
};


int main()
{
    Point pt(1,1);  
    pt.x = 10;              
    pt.set(10,10);      
    pt.print();     
}

 

 

 

상수 객체를 선언했을 때는 객체 내 변수의 값을 상수로 취급해 값을 변경할 수 없다.
또한 클래스 내에 정의된 상수 함수만 호출이 가능하다.

상수 함수로 선언된  print()는 정상 동작한다.

#include <iostream>

class Point
{
public:
    int x, y;
    Point(int a = 0, int b = 0) : x(a), y(b) {}
    
    void set( int a, int b ) { x = a; y = b; }
    
    void print() const   // 상수 멤버 함수 : 나는 값을 안 바꿀 거야 라고 약속 (=const)
    {
        std::cout << x << ",  " << y << std::endl;
    }
};


int main()
{
    const Point pt(1,1);  // 상수 객체
    pt.x = 10;      // error          
    pt.set(10,10);  // error      
    pt.print();     // print()를 상수 함수로 만들었기에 OK
}

main.cpp:21:12: error: assignment of member ‘Point::x’ in read-only object

main.cpp:22:17: error: passing ‘const Point’ as ‘this’ argument discards qualifiers [-fpermissive]

 

상수 멤버 함수 : 선언부, 구현부 예제

 

함수를 선언과 구현으로 분리할 때는 선언과 구현 모두 const를 표기해야 한다.

상수 멤버 함수 선언부, 구현부 모두 const를 붙여야 한다.

 

// =========== Point.h ============
  
class Point
{
public:
    int x, y;

    Point(int a = 0, int b = 0);
    void set(int a, int b);
    void print() const ;        // 선언부에서 const 로 선언!
};

// =========== Point.cpp ============

#include "Point.h"
#include <iostream>

Point::Point(int a, int b) 
    : x(a), y(b)
{
    
}    
void Point::set( int a, int b ) 
{
     x = a; y = b; 
}
void Point::print() const     // 구현부에도 역시 const
{
    std::cout << x << ",  " << y << std::endl;
}

 

만약 위의 구현부(Point.cpp)에서

void Point::print() const 가 아닌

void Point::print() 로 구현을 하면 아래와 같은 에러가 발생한다.

Point.h:10:10: error: candidate is: void Point::print() const

 

 

상수 멤버 함수 with "call by reference"

 

foo 의 input 값이 r 인데 , 이 r 은 const로 정의한다. r.getArea()를 호출하려면 getArea()가 const로 정의되어 있어야 한다. 

초보 개발자는 int getArea() 에 const를 붙이지 않고, 그냥 void foo( const Rect& r) 의 const를 지워 해결하는 경우가 많다.

const가 들어가야 하는 부분 (함수)에 정확하게 const를 넣어주어야 한다.

 

"객체의 상태를 변경하지 않는 모든 멤버 함수는 반드시 상수 멤버 함수로 만들어야 한다."

class Rect
{
    int xpos, ypos, width, height;
public:
    Rect( int x = 0, int y = 0, int w = 0, int h = 0 ) 
        : xpos(x), ypos(y), width(w), height(h) {}
        
    int getArea() const { return width * height;}  // 여기에 const를 적어 주어야
                                                   // foo에서 getArea사용 가능
                                                   // r 이 const 이기 때문에 ~!!
};

//void foo( Rect r )    // call by value 사용하지 않기!
void foo( const Rect& r )   // call by reference : r은 상수 객체
{
    int n = r.getArea();
}

int main()
{
    Rect r(1, 1, 10, 10); // 일반 객체 (비 상수 객체)
    
    int n = r.getArea();
    
    foo(r);
}

 

mutable

 

mutable 멤버 데이타 : 상수 멤버 함수 안에서도 값을 변경할 수 있다.

 

#include <iostream>

class Point
{
    int x, y;
    mutable int cnt = 0;   // 상수 함수에서 접근/수정 가능하게 mutable 옵션 준다.
public:
    Point(int a = 0, int b = 0) : x(a), y(b) {}

    void print() const 
    {
        ++cnt;   // 상수 함수 안에서 mutable 변수의 값은 변경할 수 있다.
        std::cout << x << ",  " << y << std::endl;
        std::cout << cnt  << std::endl;
    }
};

int main()
{
    Point pt(1,1);
    pt.print();
    pt.print();
}

cnt 값이 증가하는 것을 확인할 수 있다.

 

상수 객체, 비상수 객체의 우선 순위 및 에러 케이스

 

아래와 같이 Test 클래스에 foo() 함수가 2개 존재하는데 하나는 const 가 있고, 하나는 const가 없다.

Test t1 객체를 생성해 foo()를 호출하면 1번 foo()가 호출된다.

cosnt Test t2 객체를 생성해 foo()를 호출하면 2번 foo()가 호출된다.

 -> 동일 이름의 상수 멤버 함수와 비 상수 멤버 함수를 만들 수 있다.

#include <iostream>

class Test
{
public:
    void foo() {  // 1
        std::cout << "1" <<std::endl;
    }
    void foo() const { // 2
        std::cout << "2" <<std::endl;
    }
};

int main()
{
    Test t1;
    t1.foo();  // 1 번호출, 없으면 2번 호출
    
    const Test t2;
    t2.foo();   // 2번 호출, 없으면 error
}

 

위와 동일한 코드에서 1번 foo()를 주석처리 했을 때, 실행 결과는 아래와 같다.

t1, t2 모두 정상적으로 2번 foo()가 호출된다.

#include <iostream>

class Test
{
public:
    //void foo() {  // 1
    //    std::cout << "1" <<std::endl;
    //}
    void foo() const { // 2
        std::cout << "2" <<std::endl;
    }
};

int main()
{
    Test t1;
    t1.foo();  // 1 번호출, 없으면 2번 호출
    
    const Test t2;
    t2.foo();   // 2번 호출, 없으면 error
}

 

하지만 반대로 2번 foo()를 주석처리 했을 때, 실행 결과는 아래와 같이 에러가 발생함을 알 수 있다.

#include <iostream>

class Test
{
public:
    void foo() {  // 1
        std::cout << "1" <<std::endl;
    }
    //void foo() const { // 2
    //    std::cout << "2" <<std::endl;
    //}
};

int main()
{
    Test t1;
    t1.foo();  // 1 번호출, 없으면 2번 호출
    
    const Test t2;
    t2.foo();   // 2번 호출, 없으면 error
}

 

main.cpp:20:12: error: passing ‘const Test’ as ‘this’ argument discards qualifiers [-fpermissive]

 

const 리턴 타입

 

리턴하는 타입이 const일 때 함수의 리턴 타입 앞에도 const를 넣어 줘야 한다.

 

아래 코드는 문제 없어 보이지만 에러가 발생한다. 리턴하는 타입이 const int*인데 getData() 앞에 선언된 리턴 타입은 int*로 되어 있기 때문에 conversion 에러가 발생한다.

class Test
{
    int data;
public:
    int* getData() const
    {
        // data는 상수이다 : const int data
        return &data; 
    }
};

int main(){}

main.cpp:8:16: error: invalid conversion from ‘const int*’ to ‘int*’ [-fpermissive]

 

아래와 같이 리턴 타입을 const int* 로 선언하면 문제없이 컴파일이 된다.

class Test
{
    int data;
public:
    const int* getData() const 
    {
         // 해당 코드처럼 주소값을 리턴하는 코드는 안 좋은 코드임.
         // 예시를 위한 코드라는 점!
         return &data;
    }
};

int main(){}

 

 

 

댓글