ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [const member function] 이면에 있는 조금 더 복잡한 것
    개발/C·C++ 2021. 4. 22. 22:21

    클래스에 정의한 함수를 멤버 함수라고 합니다. 변수는 멤버 변수라고 하지요. 클래스로 객체를 만들면 객체를 위한 메모리가 할당이 될 겁니다. 여러 객체를 만들면 각 객체에 메모리가 할당됩니다. 함수도 객체의 수만큼 만들어질까요? 변수는 객체마다 새로 만들어져야 하는 것이 맞지만, 함수는 A class로 만든 a 객체의 hi() 함수나, A class로 만든 b 객체의 hi() 함수가 다르지 않습니다. 다음은 객체를 두 개를 만들어서 각자 함수를 호출한 뒤 중단점을 걸어서 주소를 확인한 결과입니다. 변수는 객체마다 다른 데이터를 담아야 하겠지만 함수는 기능 단위이기 때문에 객체마다 새로 만들 필요가 없습니다. 

    멤버 함수 안에 멤버 변수가 있을 경우에 접근하는 방법이 있어야 할 것입니다. 개발자는 자연스럽게 멤버 함수 안에서 멤버 변수에 제약 없이 접근할 수 있지만, 그것은 인간의 관점에서 의미적으로 이해되는 영엽니다. 컴파일러 입장에서는 각 멤버 함수는 오직 하나만 생성되므로 어떤 객체에서 호출했는지 판단할 수 있는 무언가가 필요합니다. (.) 연산자, (->) 연산자를 통해 non-static 멤버 함수를 호출하면 암시적으로 첫 번째 파라미터가 생깁니다. 바로 this 포인터입니다. 인자로 같이 넘어가기 때문에 멤버 변수에 접근할 수 있습니다. 이는 연산자 오버로딩에서도 마찬가지입니다.

     

    non-stataic 멤버 함수에 const를 붙이면 const 객체에서만 non-static const member function을 호출할 수 있습니다. 단순하게 같은 const니까 맞춰줘야 한다는 말은 결과적으로 그렇지만 자세하게는 위에서 언급한 이유 때문입니다. non-static 멤버 함수를 cosnt로 선언하면 첫 번째 파라미터인 this 포인터를 const로 받습니다. 아래 예제를 보면 *this가 const로 되는 것이기 때문에 (*this)로 접근하는 멤버 변수의 값을 바꿀 수 없게 됩니다.

    class Person
    {
        float _height;
        float _weight;
        
     public:
        Person(float height, float weight): _height(height), _weight(weight){}    
        float getHeight(/*Person* this*/)
        {
           return _height; // return (*this)._height;
        }
        
        float getWeight(/*const Person* this*/) const
        {
           return _weight; // return (*this)._weight;
        }
    }
    
    int main()
    {
        Person person0(176.f, 60.f);
        person0.getHeight(); // -> getHeight(&person0)
        person0.getWeight(); // -> getWeight(&person0)
        
        const Person person1(178.f, 55.f);
        person1.getHeight(); // -> getHeight(&person1) // error
        person1.getWeight(); // -> getWeight(&person1)   
    }

    여기에서 person0을 const로 선언하면 non-static 일반 함수(non-const)를 호출하지 못합니다. 그 이유는 다음 예제를 보고 나서 확인해보겠습니다.

    const int* pNum1는 *pNum1을 변경하지 못한다는 의미입니다. 풀어서 말해 pNum1의 주소를 역참조(dereferencing)하는 값을 변경하지 못합니다. pNum0에 pNum1을 대입하는 것을 허용해버리면, pNum1이 가리키는 것을 변경할 수 있게 돼버립니다. 반대로 pNum1에 pNum0을 대입하면 pNum0이 역참조하는 값을 변경할 수 있었다가 변경할 수 없게 됩니다. 이는 허용합니다. const 객체가 non-static const 멤버 함수를 호출할 수 있고 일반 멤버 함수를 호출하지 못하며 일반 객체는 non-static인 일반 멤버 함수, const 멤버 함수 모두를 호출하는 것이 설명됩니다.

     

    멤버 함수에 const를 붙이는 이유는 멤버 변수를 변경하지 않는다는 사실을 명확하게 표현하기 위해서입니다. 혹은 조작을 하면 안 된다는 것을 알리는 목적으로도 사용할 수 있습니다. 개발자가 객체 하나를 꼭 const로 만들어야 하는데 필요한 함수가 non-const이라면 어쩔 수 없이 객체를 non-const로 선언해야 합니다. 분명 const로 객체를 만들어야 하는 이유가 있었을 것입니다. 좋지 않은 상황입니다. const를 쓸 수 있는 상황에서는 되도록 써주는 편이 좋다고 판단됩니다.

     

    출처>

    패스트캠퍼스 C++ 올인원

    docs.microsoft.com/en-us/cpp/cpp/function-overloading?view=msvc-160

    eel.is/c++draft/over.match.funcs

    댓글

Designed by Tistory.