업데이트:

태그: ,

카테고리:



REF : 윤성우의 열혈 C++

정보은닉과 캡슐화

정보은닉의 이해

제한된 방법으로의 접근만 허용해 잘못된 값이 저장되지 않도록 도와야한다.
실수가 쉽게 발견되도록 해야한다.

CPP에서 이를 위해서

  • 멤버변수는 private로 선언한다.
  • private에 접근할 수 있는 함수를 별도로 정의한다.
  • 이로써 안전한 형태로 변수 접근을 유도할 수 있다.


const 함수

  • const 함수는 멤버변수에 저장된 값을 변경하지 않겠다라는 의미이다.
    • 따라서, const선언이 추가된 멤버함수에서 멤버변수의 값을 변경한다면, 컴파일 에러가 발생한다.

  • const함수의 내에서는 const가 아닌 함수 호출에 제한이 생긴다.
    • const가 붙지 않은 함수는 멤버변수의 값을 변경할수도 있기때문이다.

  • const 참조자를 대상으로는 const 함수만 호출할 수 있다.

const 키워드를 많이 사용할수록 코드의 안정성이 높아진다.


캡슐화

객체지향 프로그래밍에서 캡슐화는 2가지 측면이 있다.

  • 객체의 데이터와 기능을 하나로 묶고,
  • 실제 구현 애용 일부를 내부에 감추어 은닉한다.

이렇게 캡슐화에는 기본적으로 정보은닉이 포함된다.

캡슐화를 하기 위해선, 어떤 데이터, 기능을 하나로 묶을 것인지를 결정해야한다.
이에는 정답이 없고, 프로그램의 성격과 특성에 따라 달라진다.
경험많은 프로그래머일수록 이것이 명확하다.


생성자와 소멸자

생성자를 사용하면 객체를 생성하면서 동시에 멤버변수를 초기화할 수 있다.

class SimpleClass
{
private:
	int num;
public:
	SimpleClass(int n)
	{
		num = n;
	}
	int GetNum() const
	{
		return num;
	}
}

여기서 SimpleClass(int n)이라는 함수가 생성자이다.

  • 클래스의 이름과 함수의 이름이 동일하다.
  • 반환형이 선언되어 있지 않으며, 실제로 반환하지 않는다.

이러한 유형의 생성자는 객체 생성시 딱 한번 호출된다.
생성자가 가지는 특징으로는,

  1. 오버로딩이 가능하다.
  2. 매개변수에 디폴트 값을 설정할 수 있다.
SimpleClass()
{
	num1=0;
	num2=0;
}
SimpleClass(int n)
{
	num1=n;
	num2=0;
}
SimpleClass(int n1, int n2)
{
	num1=n1;
	num2=n2;
}

클래스 내부에 위와같은 생성자를 선언해두면,

  • 매개변수에 따른 함수오버로딩
  • 디폴트값

을 정할 수 있게된다.


멤버 이니셜라이저의 사용

class Rectangle
{
	private:
		Point	upLeft;
		Point	lowRight;
	public:
		Rectangle(const int &x1, const int &y1, const int &x2, const int &y2);
		void ShowRecInfo() const;
};

private로 선언된 멤버변수 upLeft와 lowRight Point 객체를 초기화해보자.
이때, 멤버 이니셜라이저를 사용할 수 있는데,

Rectangle::Rectangle(const int &x1, const int &y1, const int &x2, const int &y2) :upLeft(x1, y1), lowRight(x2, y2)
{
}

객체의 생성과정에서 Point 객체의 생성자를 호출하면서 이 결과를 Rectangle 객체의 멤버변수로 넣게된다.


C++의 모든 객체는 세가지 과정을 순서대로 거쳐서 생성이 완성된다.

  • 1단계 : 메모리 공간의 할당
  • 2단계 : 이니셜라이저를 이용한 멤버변수(객체)의 초기화
  • 3단계 : 생성자의 몸체부분 실행

생성자를 명시적으로 호출하지 않더라도, default constructor가 자동으로 삽입되어 호출되는 것을 기억해야한다.



멤버 이니셜라이저를 이용한 변수 및 const 상수 초기화

멤버 이니셜라이저는 객체가 아닌 멤버의 초기화에도 사용할 수 있다.

class SoSimple
{
private:
	int num1;
	int num2;
public:
	SoSimple(int n1, int n2) : num1(n1)
	{
		num2=n2;
	}
}
  • num1(n1) : num1을 n1의 값으로 초기화

  • 멤버변수를 초기화하는 방법은

    • 생성자의 몸체에서 초기화
    • 이니셜라이저를 사용
    • 2가지가 존재하는데, 일반적으로 이니셜라이저를 선호한다.
      1. 초기화 대상을 명확히할 수 있다.
      2. 성능에 약간의 이점이 있다.
        • num2=n2와 같은 코드는
            int num2;
            num2=n2;
          

          이므로, 2번의 과정을 거쳐야하지만,
          이니셜라이저를 이용하면 선언과 동시에 초기화가 이뤄지는 형태로 바이너리 코드가 생성된다.

        • 따라서, const키워드가 달린 멤버변수도 이니셜라이저로 초기화할 수 있다.


이니셜라이저로 const 멤버변수 초기화

class SoSimple
{
	private:
		const int mynum1;
		const int mynum2;
	public:
		SoSimple(int num1, int num2) : mynum1(num1), mynum2(num2)
}


이니셜라이저로 멤버변수를 참조자로 선언하기

const 변수처럼 참조자도 선언과 동시에 초기화가 이뤄져야한다.
따라서, 선언과 동시에 초기화가 이뤄지는 형태로 바이너리 코드를 구성하는 이니셜라이저의 특성을 이용하면
참조자인 멤버변수를 만들 수 있다.
다만 이런 경우가 흔하지는 않다.

class AnotherCls
{

}
class SoSimple
{
	private:
		AnotherCls &ref;
	public:
		SoSimple(AnotherCls &r) : ref(r)
}


디폴트 생성자

객체가 되기 위해서는 반드시 하나의 생성자가 호출되어야한다.
C++에서는 생성자가 정의되지 않은 클래스에는 컴파일러가 디폴트 생성자를 자동으로 삽입한다.

  • 아무런 인자를 받지 않는다.
  • 내부적으로 아무런 일도 하지 않는 생성자이다.
class SoSimple
{
	private:
		int num;
	public:
		int GetNum{return Num;}
};

생성자가 없는 위의 클래스는

class SoSimple
{
	private:
		int num;
	public:
		SoSimple(){}
		int GetNum{return Num;}
};

이 코드와 동일하다.


private 생성자

객체의 생성이 클래스의 외부에서 진행되기 때문에 생성자는 public으로 선언되어야한다.

하지만, 클래스의 내부에서 객체를 생성한다면, 생성자를 private으로 해도 무방하다.

class AAA
{
	private:
		int num;
	public:
		AAA() : num(0) {}
		AAA& CreateInitObj(int n) const
		{
			AAA *ptr = new AAA(n);
			return (*ptr);
		}
		void ShowNum() const
		{
			cout<<num<<endl;
		}
	private:
		AAA(int n) : num(n)
		{

		}
};
  • CreateInitObj함수는 힙영역에 할당된 객체를 참조자의 형태로 반환해 외부에서 변수처럼 사용할 수 있도록한다.
  • 힙에 할당된 메모리 공간은 변수로 간주해 참조자를 통한 참조가 가능하다.


소멸자의 이해와 활용

생성자는 객체의 생성시 호출되는데,

  • 소멸자는 객체소멸시 반드시 호출된다.
  • 소멸자는 ~가 붙은 형태의 이름을 가진다.
  • 반환형이 선언되어 있지 않으며, 실제로 반환하지 않는다.
  • 매개변수는 void형으로 선언되어야하기 때문에 오버로딩도, 디폴트 값 설정도 불가능하다.
  • 소멸자는 객체소멸 과정에서 자동으로 호출이된다.
  • 직접 소멸자를 정의하지 않으면 디폴트 소멸자가 자동으로 삽입된다.
class Simple
{
public:
	Simple() {}
	~Simple() {}
};



클래스와 배열 그리고 this 포인터

객체배열

  • 선언
      Sosimple arr[10];
      SoSimple * ptrArr = new SoSimple[10];
    
  • 배열로 선언할 경우, 생성자에 인자를 전달하지 못한다.
    • 따라서, 인자가 없는 생성자가 정의되어있어야한다.
    • 원하는 값으로 초기화해야한다면, 일일히 초기화의 과정을 별도로 거쳐야한다.


객체 포인터 배열

  • 객체 포인터 배열은 객체의 주소 값 저장이 가능한 포인터 변수로 이뤄진 배열이다.
SoSimple *arr[3];

int	i = 0;
while (i < 3)
{
 arr[i] = new SoSimple();
 i++;
}


this 포인터

  • 멤버함수 내에서는 this라는 이름의 포인터를 사용할 수 있다.
  • 객체 자신을 가리키는 용도로 사용한다.
SoSimple *GetThisPointer()
{
	return this;
}


this 포인터 활용

class ThisClass
{
private:
	int num;
public:
	void ThisFunc(int num)
	{
		this->num=207;
		num=105;
	}
}
  • 매개변수의 이름과 멤버변수의 이름이 같을 때,
    • 멤버변수에 접근할 때 this를 이용할 수 있다.


Self-Reference의 반환

Self-Reference란 객체 자신을 참조할 수 있는 참조자를 의미한다.

SelfRef& Adder(int n)
{
	num += n;
	return *this;
}
  • this가 포인터이므로 이를 참조하면 객체 자신이 된다.
  • 반환형이 참조자이므로, 객체 자신의 참조자가 반환된다.


댓글남기기