개인 공부/C++

[C++] Chapter 04 정리

Koalitsiya 2023. 3. 29. 17:53

정보은닉(Information Hiding)

좋은 클래스가 되기 위한 최소한의 조건은 '정보은닉'과 '캡슐화'이다.

 

 멤버변수가 public으로 선언된다면 어디서든 접근이 가능해지기 때문에 문제가 발생할 수 있다.

 이러한 문제를 막기 위해 제한된 방법으로의 접근만 허용하여 잘못된 값이 저장되지 않도록 해야하며, 또한 실수가 쉽게 발견될 수 있도록 해야한다.

class Car {
private:
	int fuel;
    int speed;
public:
	void InitCarState(int fuel, int speed);
    int GetFuel() const;
    bool SetFuel(int fuel);
    void Accel();
};

 

 이렇게 멤버변수를 private으로 선언하고, 해당 변수에 접근하는 함수를 별도로 정의해서, 안전한 형태로 멤버변수의 접근을 유도하는 것이 '정보은닉'이다.

 

 위의 코드에서 아래와 같이 'Get멤버변수명', 'Set멤버변수명'으로 정의된 함수들을 '액세스 함수(Access Function)'라 하며 이러한 액세스 함수들은 멤버변수를 private으로 선언하면서 클래스 외부에서 멤버변수로의 접근을 목적으로 정의되는 함수들이다.

int GetFuel() const;
bool SetFuel(int fuel);

 추가로 앞에서 선언된 함수에 const 선언이 추가되어 있는 경우가 있는데, 이는 해당 함수 내에서는 멤버변수에 저장된 값을 변경하지 않겠다는 의미이다. 또한 이렇게 선언 시 const 함수 내에서는 const가 아닌 함수의 호출이 제한됨으로써 코드의 안정성을 높일 수 있다.

 

 

캡슐화(Encapsulation)

 정보은닉과 함께 객체지향 기반의 클래스 설계에서 가장 기본이면서도 중요한 원칙들이다.

 

 캡슐화를 하게 되면 상호관계가 복잡했던 것을 줄일 수 있게 되고 프로그램을 간결하게 만들 수 있으나 캡슐화의 범위를 결정하는 일이 쉽지 않기 때문에 어려운 개념이기도 하다. 그렇기 때문에 다양한 사례를 접하며 클래스를 캡슐화 시키는 능력을 기를 필요가 있다.

 

 

생성자(Constructor)와 소멸자(Destructor)

 지금까지 멤버함수를 통해 멤버변수를 초기화 시켰지만, '생성자'를 이용해 객체를 생성과 동시에 초기화할 수 있다.

class SimpleClass {
private:
    int num;
public:
    SimpleClass(int n) { // 생성자(Constructor)
        num = n;
    }
    int GetNum() const {
        return num;
    }
};

 

 위의 클래스에서 클래스의 이름과 함수의 이름이 동일하고, 반환형이 선언되어 있지 않고, 실제로 반환하지 않는 함수가 있다. 이러한 유형의 함수를 가리켜 '생성자(Constructor)'라 하며, 이 함수는 개체 생성시 딱 한 번 호출된다. 

 

 생성자 또한 함수의 일종이므로 오버로딩이 가능하며, 매개변수의 디폴트 값 설정도 가능하다.

class SimpleClass {
public:
	int num1;
	int num2;
private:
	SimpleClass() {
		num1 = 0;
		num2 = 0;
	}
	SimpleClass(int n) {
		num1 = n;
		num2 = 0;
	}
	SimpleClass(int n1, int n2) {
		num1 = n1;
		num2 = n2;
	}
};

 

위에서 정의된 생성자를 통해 객체를 생성하려면 아래와 같이 문장이 구성된다.

  • SimpleClass sc2(100);
  • SimpleClass * ptr2 = new SimpleClass(100);
  • SimpleClass sc3(100, 200);
  • SimpleClass * ptr3 = new SimpleClass(100, 200);

그러나 아래와 같은 문장은 허용되지 않는다.

  • SimpleClass sc1();

대신 아래와 같이 구성해야 한다.

  • SimpleClass sc1;
  • SimpleClass * ptr1 = new SimpleClass;
  • SimpleClass * ptr1 = new SImpleClass();

 이는 main 함수내에서 Simpleclass sc();와 같은 문장이 선언되면 객체를 생성하기 위한 객체생성문인지 함수의 원형선언인지 구분할 수 없기 때문에 C++에서는 이를 함수의 원형선언에만 사용하기로 약속하였다.

 

 

멤버 이니셜라이저(Member Initializer)

 

  객체의 생성과정에서 그 객체의 멤버변수로 선언된 객체의 생성자를 호출하여 멤버변수로 선언된 객체를 초기화할 수 있으며 이에 사용되는 것이 '멤버 이니셜라이저(Member Initializer)'이다.

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

 

 위 코드에서 :upLeft(x1, y1), lowRight(x2, y2)에 해당하는 부분이 '멤버 이니셜라이저'이며 이것은 아래의 의미를 가진다.

  • 객체 upLeft의 생성과정에서 x1, y1을 인자로 전달받는 생성자를 호출
  • 객체 lowRight의 생성과정에서 x2, y2를 인자로 전달받는 생성자를 호출

 멤버 이니셜라이저를 사용하다 보면, 생성자의 몸체가 비어있는 경우가 발생하나 그냥 무시하면 된다.

 

 멤버 이니셜라이저는 객체가 아닌 멤버의 초기화에서도 사용할 수 있으며 아래와 같은 두 가지 장점이 있기 때문에 이니셜라이저를 통해 멤버번수를 초기화 하는 것이 선호된다.

  • 초기화의 대상을 명확히 인식할 수 있다.
  • 성능에 약간의 이점이 있다.

 또한, 이니셜라이저를 이용하면 선언과 동시에 초기화가 이뤄지는 형태로 바이너리 코드가 생성되므로 const 멤버변수도이니셜라이저를 통해 초기화가 가능하며 이러한 특징은 멤버변수로 참조자를 선언할 수 있게 한다.

 

 

디폴트 생성자(Default Constructor)

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

 

private 생성자

 객체의 생성이 클래스의 외부에서 진행되면 생성자는 public으로 선언되어야 한다. 하지만 클래스 내부에서 객체를 생성한다면, private으로 선언될 수 있다.

 

소멸자의 이해와 활용

 객체 생성 시 반드시 호출되는 것이 생성자라면, 객체 소멸 시 만드시 호출되는 것을 소멸자라 하고, 아래와 같은 형태를 갖는다.

  • 클래스의 이름 앞에 '~'가 붙은 형태의 이름을 갖는다.
  • 반환형이 선언되어 있지 않으며, 실제로 반환하지 않는다.
  • 매개변수는 void형으로 선언되어야 하기 때문에 오버로딩도, 디폴트 값 설정도 불가능하다.

 소멸자도 생성자와 마찬가지로 소멸자가 정의되어 있지 않으면 디폴트 소멸자가 자동으로 삽입된다.

 

 소멸자는 대체로 생성자에서 할당한 리소스의 소멸에 사용되며, 생성자에서 new 연산자를 이용해 할당해 놓은 메모리 공간이 있다면, 소멸자에서는 delete 연산자를 이용해서 메모리 공간을 소멸한다.

 

 

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

객체 배열

 객체 기반의 배열은 다음의 형태로 선언한다.

  • SimpleClass arr[10];
  • SimpleClass * ptrArr = new SimpleClass[10];

this 포인터

 멤버함수 내에서는 this라는 이름의 포인터를 사용할 수 있고, 이는 객체 자신을 가리키는 용도로 사용되는 포인터이다.

class SimpleClass {
private:
	int num;
public:
	SimpleClass * GetThisPointer( {
    	return this;
    }
    void ThisFunc(int num) {
    	this->num = 200;
        num = 100;
    }
};

 

 이러한 특성을 이용해 멤버변수와 매개변수의 이름을 동일하게 짓고, this 포인터를 이용해서 이 둘을 구분하기도 한다.

 

Self-Reference의 반환

 Self-Reference란 객체 자신을 참조할 수 있는 참조자를 의미하며, this 포인터를 이용해 객체가 자신의 참조에 사용할 수 있는 참조자의 반환문을 구성할 수 있다.

 

참조의 정보(참조 값)에 대한 이해

int main() {
	int num = 7;
    int &ref = num;
    ...

 이것은 다음과 같이 표현하기도 한다.

"변수 num을 참조할 수 있는 참조 값이 참조자 ref에 전달되어, ref가 변수 num을 참조하게 된다."