우리는 객체의 생성을 목적으로 클래스를 디자인.
좋은 클래스가 되기 위한 조건으로는 '정보 은닉'과 '캡슐화'
정보은닉의 이해
윈도우 그림판 프로그램 실행 시 다양한 형태의 도형을 그릴 수 있다.
이와 유사한 성격의 프로그램 C++을 이용해서 구현.
위치좌표를 표현하는 목적의 클래스는 기본으로 필요
class point
{
public:
int x;
int y;
}
일단 위의 클래스에서 멤버변수
x와 y의 범위는 0이상 100이하이고,
좌 상단 좌표 [0, 0], 우 하단 좌표가 [100, 100]이라고 가정.
다음 예는, Point 클래스의 멤버변수가 public으로 선언되었을 때 발생할 수 있는 문제점이다
#include <iostream>
using namespace std;
class Point
{
public:
int x;
int y;
};
class Rectangle
{
public:
Point upleft;
Point lowright;
public:
void showrecinfo()
{
cout << "좌 상단 : " << '[' << upleft.x << ", ";
cout << upleft.y << ']' << endl;
cout << "우 하단 : " << '[' << lowright.x << ", ";
cout << lowright.y << ']' << endl << endl;
}
};
int main(void)
{
Point pos1 = { -2, 4 };
Point pos2 = { 5, 9 };
Rectangle rec = { pos2, pos1 };
rec.showrecinfo();
return 0;
}
Rectangle rec;
위와 같이 클래스 객체가 생성되면 메모리 상에 아래와 같이 존재
Rectangle 객체 안에 두 개의 Point 객체가 포함.
객체가 멤버로 등장했다고 해서 특별히 달라지는 건 없다.
Rectangle rec = { pos2, pos1 };
위 문장은 객체를 생성하고 초기화하는 문장.
미리 생성해 놓은 두 개의 Point 객체에 저장된 값이 Rectangle 객체의 멤버에 대입이 된다.
문제점
- 점의 좌표는 0이상 100이하가 되어야 하는데, 그렇지 못한 Point 객체가 있다.
- 직사각형을 의미하는 Rectangle 객체의 좌 상단 좌표 값이 우 하단 좌표 값보다 크다.
"프로그래머의 실수에 대한 대책이 하나도 준비되어 있지 않다"
프로그래머가 실수 했을 때, 이는 어떻게든 발견되어야 한다.
제한된 방법으로의 접근만 허용을 해서 잘못된 값이 저장되지 않도록 도와야 하고, 또 실수를 했을 때, 실수가 쉽게 발견되도록 해야 한다.
Point.h
#ifndef __POINT_H__
#define __POINT_H__
class Point
{
private:
int x;
int y;
public:
bool init(int xpos, int ypos);
int GetX() const;
int GetY() const;
bool SetX(int xpos);
bool SetY(int ypos);
};
#endif
Point.cpp
#include <iostream>
#include "Point.h"
using namespace std;
bool Point::init(int xpos, int ypos)
{
if (xpos < 0 || ypos < 0)
{
cout << "벗어난 범위의 값 전달" << endl;
return false;
}
x = xpos; // Point 객체의 private 변수에 값 대입
y = ypos;
return true;
}
int Point::GetX() const // const 함수!
{
return x;
}
int Point::GetY() const // const 함수!
{
return y;
}
bool Point::SetX(int xpos)
{
if (xpos < 0 || xpos >100)
{
cout << "벗어난 범위의 값 전달" << endl;
return false;
}
x = xpos;
return true;
}
bool Point::SetY(int ypos)
{
if (ypos < 0 || ypos >100)
{
cout << "벗어난 범위의 값 전달" << endl;
return false;
}
y = ypos;
return true;
}
init, SetX, SetY는 0이상 100이하의 값이 전달되지 않으면, 에러 메세지 출력 후 값의 저장을 허용하지 않은 형태로 정의
따라서 잘못된 값이 저장되지 않을뿐더러, 값이 잘못 전달되는 경우 출력된 메세지를 통해서 문제가 있음을 확인.
"멤버변수를 private으로 선언하고, 해당 변수에 접근하는 함수를 별도로 정의해서, 안전한 형태로 멤버변수의 접근을 유도하는 것이 바로 '정보은닉'이며, 이는 좋은 클래스가 되기 위한 기본조건이 된다."
코드를 보면 함수 이름이 아래처럼 되어 있음
int GetX() const;
bool SetX(int xpos);
int GetY() const;
bool SetY(int ypos);
이들을 가리켜 '액세스 함수(access function)'라 하는데, 이들은 멤버변수를 private으로 선언하면서 클래스 외부에서 멤버변수 접근을 목적으로 정의되는 함수들.
호출되지 않지만 나중에 필요할 수 있다고 판단되는 함수들도 정의하곤 함
Rectangle.h
#ifndef __RECTANGLE_H_
#define __RECTANGLE_H_
#include "Point.h"
class Rectangle
{
private:
Point upleft;
Point lowright;
public:
bool init(const Point& ul, const Point& lr);
void showinfo() const;
};
#endif
Rectangle.cpp
#include <iostream>
#include "Rectangle.h"
using namespace std;
bool Rectangle::init(const Point& ul, const Point& lr)
{
if (ul.GetX() > lr.GetX() || ul.GetY() > lr.GetY())
{
cout << "잘못된 위치정보 전달" << endl;
return false;
}
upleft = ul;
lowright = lr;
return true;
}
void Rectangle::showinfo() const
{
cout << "좌 상단:" << '[' << upleft.GetX() << ", ";
cout << upleft.GetY() << ']' << endl;
cout << "우 상단:" << '[' << lowright.GetX() << ", ";
cout << lowright.GetY() << ']' << endl << endl;
}
함수를 통해서 멤버변수의 접근에 제한을 두었다는 사실에 주목
main.cpp
#include <iostream>
#include "Point.h"
#include "rectangle.h"
using namespace std;
int main(void)
{
Point pos1;
if (!pos1.init(-2, 4))
cout << "초기화 실패" << endl;
if (!pos1.init(2, 4))
cout << "초기화 실패" << endl;
Point pos2;
if (!pos2.init(5, 9))
cout << "초기화 실패" << endl;
Rectangle rec;
if (!rec.init(pos2, pos1))
cout << "직사각형 초기화 실패" << endl;
if (!rec.init(pos1, pos2))
cout << "직사각형 초기화 실패" << endl;
rec.showinfo();
return 0;
}
모든 초기화 함수들이 초기화의 실패 여부에 따라서 true 또는 false를 반환하도록 정의했기 대문에, 함수를 호출한 영역에서 성공여부를 확인하고 그에 따른 조치를 취할 수 있다.
const 함수
int GetX() const;
int GetY() const;
void showinfo() const;
"이 함수 내에서는 멤버변수에 저장된 값을 변경하지 않겠다!"
매개변수도 아니고, 지역변수도 아닌, 멤버변수에 저장된 값을 변경하지 않겠다는 선언.
따라서 const 선언이 추가된 멤버함수 내에서 멤버변수의 값을 변경하는 코드가 사빕되면, 컴파일 에러가 발생
const로 선언하면, 실수로 자신의 의도와 다르게 멤버변수의 값을 변경했을 때, 컴파일 에러를 통해서 이를 확인 가능
const 함수의 또 다른 특징이 있음
class simpleclass
{
private:
int num;
public:
void init(int n)
{
num = n;
}
int GetNUM() // const 선언이 추가되어야 아래의 컴파일 에러 소멸
{
return num;
}
void shownum() const
{
cout << GetNUM() << endl; // 컴파일 에러 발생
}
};
shownum 함수는 const 함수로 선언,
실제로 함수 내에서 멤버변수 num의 값을 변경하지 않는다.
그럼에도 불구하고 GetNum 함수를 호출하는 문장에서 컴파일 에러가 발생.
이유
"const 함수 내에서는 const가 아닌 함수의 호출이 제한된다!"
const로 선언되지 않은 함수는 암누리 멤버변수에 저장된 값을 변경하지 않더라도, 변경할 수 있는 능력을 지닌 함수.
따라서 이러한 변경이 가능한 함수의 호출을 아예 허용하지 않는 것.
유사한 예시
#include <iostream>
using namespace std;
class easyclass
{
private:
int num;
public:
void init(int n)
{
num = n;
}
int GetNUM() // const 선언이 추가되어야 아래의 컴파일 에러 소멸
{
return num;
}
};
class hiclass
{
private:
int num;
public:
void init(const easyclass& easy)
{
num = easy.GetNUM(); // 컴파일 에러 발생
}
};
위 클래스 정의에서 init 함수의 매개변수 easy는 'const 참조자'이다.
그런데 이를 대상으로 GetNUM 함수를 호출하면 컴파일 에러 발생
GetNUM이 const 함수가 아니기 때문
const 선언을 한번 사용하기 시작하면, 코드의 안전성은 높아질 것이다.
참고 : [윤성우 열혈 C++ 프로그래밍] - 대부분의 내용 및 코드는 이 책에서 개인 공부 정리 목적으로 참고하였습니다.
'Language&Framework&Etc > C++' 카테고리의 다른 글
클래스의 완성(04-3) 생성자(Constructor)와 소멸자(Destructor)-1 (0) | 2020.12.25 |
---|---|
클래스의 완성(04-2) 캡슐화(Encapsulation) (0) | 2020.12.25 |
클래스의 기본(03-3) 객체지향 프로그래밍의 이해 (0) | 2020.12.24 |
클래스의 기본(03-2) 클래스(Class)와 객체(Object)-2 (0) | 2020.12.24 |
클래스의 기본(03-2) 클래스(Class)와 객체(Object)-1 (0) | 2020.12.24 |