C++ Chapter 8.9 : 클래스와 const

Date:     Updated:

카테고리:

태그:

인프런에 있는 홍정모 교수님의 홍정모의 따라 하며 배우는 C++ 강의를 듣고 정리한 필기입니다. 😀
🌜 [홍정모의 따라 하며 배우는 C++]강의 들으러 가기!


chapter 8. 객체 지향의 기초 : 클래스와 const

🔔 const 가 붙은 객체

const Friend f;

f.setValue(2); // 💥에러! 불가능
f.getValue(2); // 💥에러! 불가능 
  • 객체 선언시 const가 붙는 것의 의미
    • Friend 타입의 객체 f멤버의 값을 바꿀 수 없다.
      • 따라서 setter 접근 함수 사용이 불가능 해진다.
      • 근데 의외로 getter는 멤버의 값을 바꾸지 않는데도 불구하고 getter 접근 함수의 사용도 불가능해진다.
        • 멤버를 바꾸지 않는 것을 확실히 하는 const 멤버 함수만 사용 가능하기 때문!
        • 컴파일러는 멤버 값을 바꿨는가 아닌가로 판단하는 것이 아닌 멤버 함수가 const인가 아닌가로 판단한다.

뒤에 const가 붙은 멤버 함수만 사용 가능

객체를 생성할 때 const를 붙이면 멤버 값을 변경하는게 불가능하다. 그리고 이 객체는 const가 붙은 멤버 함수만 사용할 수 있다.

뒤에 const가 붙을 수 있는건 멤버 함수만 가능하다. 일반 전역 함수는 뒤에 const를 붙일 수 없다.

int getValue() const
{
    return m_value;
}
const Friend f;

f.setValue(2); // 💥에러! 불가능
f.getValue(2); // ⭐이제 가능⭐

멤버 함수 이름 뒤에 const를 붙여 멤버 값을 변경하지 않을 것이라는걸 확실히 해준다. 객체가 const로 생성되었을시 const가 붙은 멤버 함수만 사용할 수 있다. setter 함수는 그 자체로 멤버의 값을 변경하는 함수기 때문에 const 멤버 함수로 만들 수 없다. const 붙여도 오류가 난다.

  1. const가 붙지 않은 일반 객체도 const멤버 함수를 호출할 수 있다. (호출 못 할거라고 착각했었다 😥) 단, const가 붙은 객체는 const멤버 함수 호출할 수 있다.
  2. 매개 변수가 const 객체인 함수들은 인수로 받은 해당 객체의 멤버 함수를 사용할 때 꼭 뒤에 const가 붙은 함수만 사용할 수 있게 된다.
    • 함수에 인수로 객체를 넘길 때, 그 안에서 사용될 일이 있는 멤버 함수라면 뒤에 const를 붙여주어야 한다.
    • getter 같은 접근 함수는 많이 사용되고 또한 멤버 값을 변경하지 않기 때문에 그냥 뒤에 const를 붙여서 구현해주자.


🔔 복사 생성자

📜Something 클래스

class Something
{
public:
	int m_value = 0;

	Something()
	{
		cout << "Constructor" << endl;
	}
    
	void setValue(int value) { m_value = value; }
	int getValue() const { return m_value; }
};

📜main

void print(Something st)
{
	cout << &st << endl;
	cout << st.m_value << endl;
}

int main()
{
	Something something;
	
	cout << &something << endl;

	print(something);

	return 0;
}
💎출력💎

Constructor
0x7fff7fbdd2fc  // &st 와
0x7fff7fbdd2dc  // &something 은 주소가 다르다.
0
  • print(something)이 호출하면서 print 함수의 매개변수 st 에 메인 함수에서 생성된 something 객체가 복사된다.
    • 매개 변수이자 지역 변수인 Something st도 Something 객체로서 생성된다.
    • Something 객체가 2번 생성되니 생성자도 2번 호출되야 한다.
      1. Something something; (메인 함수)
      2. Something st = something; (print 함수의 매개변수)
    • 그러나 “Constructor”가 1번 출력되는 것을 보니 생성자가 한번만 호출됐다는 것을 알 수 있다.
      • 이유는 매개 변수에 복사되는 Something st = something 이 실행될 때 클래스에 디폴트로 숨겨져 있던 복사 생성자가 호출되었기 때문이다.
Something (const Something & st_in) // 복사 생성자
{
    m_value = st_in.m_value; // 멤버 복사
}
  • Something st = something;
    • Call by Value 형태로 something 을 전달한다.
    • 매개 변수인 Something st 객체가 생성되면서 복사 하는 같은 타입의 대상이 있기에 복사 생성자가 호출된다.
    • st_in 은 복사 없이 something 그 자체를 참조한다. (Call by Reference)
    • st_in.m_value, 즉 something 객체의 m_value 멤버 값을
    • st 객체의 멤버 m_value 에 대입(복사)한다.

클래스는 모두 const이자 같은 타입의 객체참조로 인수 1개를 받아 그 멤버들의 값을 내 멤버들에 복사하는 복사생성자를 디폴트로 가지고 있다. 숨겨져서 안보일 뿐!

  • 즉 객체가 생성될 때 같은 타입의 객체의 멤버를 복사하여 멤버를 초기화한다.
    • const
      • 복사하는 대상인 객체의 멤버 값을 변경할 수 없다.
      • 복사하는 대상인 객체의 const 멤버 함수에만 접근이 가능하다.
    • 같은 타입의 객체 그 자체(참조)를 가져와 복사한다.

디폴트로 있긴 하지만 아래 코드와 같이 프로그래머가 직접 복사생성자를 정의할 수도 있다.

class Something
{
public:
	int m_value = 0;

	Something(const Something& st_in)  // 복사 생성자를 직접 정의했다.
	{
		m_value = st_in.m_value;
		cout << "Copy constructor" << endl;
	}

	Something()
	{
		cout << "Constructor" << endl;
	}
    
	void setValue(int value) { m_value = value; }
	int getValue() const { return m_value; }
};

void print(Something st)
{
	cout << &st << endl;
	cout << st.m_value << endl;
}

int main()
{
	Something something;
	
	cout << &something << endl;

	print(something);

	return 0;
}
💎출력💎

Constructor
0x7ffd1a921798  

Copy constructor                  
0x7ffd1a92179c  

0

복사 생성자가 호출되는 것을 확인할 수 있다.

void print(const Something & st)
{
	cout << &st << endl;
	cout << st.m_value << endl;
}

위와 같이 매개변수 타입을 바꿔주면 Somethingst에 복사되는 것이 아닌 stSomething 객체 그 자체를 참조하게 되므로 복사 생성자가 호출되지 않는다. 복사 과정이 없으므로!

복사 생성자가 호출될 때

  1. 먼저 생성한 객체를 나중에 생성한 객체의 생성자 인자로 전달할 때
  2. 함수의 인자로 객체를 Call by Value 형태로 전달할 때
  3. 객체를 Call by Value 형태로 리턴할 때


🔔 const 함수의 오버로딩

만약 동일한 함수가 2개 있는데 하나는 const를 붙이고 하나는 const를 붙이지 않았다면 어떻게 오버로딩이 될까?

#include <iostream>
#include <string>

class Something
{
public:
    string m_value = "default";

    const string & getValue() const
    {
        cout << "const version" << endl;
        return m_value;
    }

    string & getValue()
    {
        cout << "non-const version" << endl;
        return m_value;
    }
}

int main()
{
    Something s1;
    s1.getValue();   // string & getValue() 호출

    const Something s2;
    s2.getValue();  // const string & getValue() const 호출
}
const string & getValue() const
  • 리턴 타입 : const string &
    • 리턴 되는 m_value 의 레퍼런스를 리턴한다.
      • 임시 공간에 복사 하는 과정 없이 그대로 리턴한다.
    • const 레퍼런스 이므로 리턴 되는 string 값을 수정할 수 없다.
      • s2.getValue() = 20 불가능.
  • 뒤에 const가 붙었으므로
    • 멤버 값을 변경할 수 없으며
    • const객체의 경우 이 함수만 호출할 수 있다.

const 의 유무도 오버로딩의 고려 대상이 된다.

  • 일반 객체로 생성된 s1
    • *s1.getValue() 호출시
    • string & getValue() 를 오버로딩한다.
  • const 객체로 생성된 s2
    • *s2.getValue() 호출시
    • const string & getValue() const 를 오버로딩한다.
      • const 객체뒤에 const가 붙은 함수를 오버로딩 한다.

보통 멤버 함수를 const로 만들 땐 리턴 타입도 const로 한다. const string & getValue() const

  • const가 앞에 붙은 함수
    • 단순히 리턴을 변경할 수 없는 상수로 하겠다는 의미
  • const가 뒤에 붙은 함수
    • const 객체만 사용할 수 있는 함수로서
    • 멤버 값을 변경하지 않겠다는 의미


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기


Cpp 카테고리 내 다른 글 보러가기

댓글 남기기