Computer Science/C & C++

[C++] Class | 클래스

lww7438 2020. 2. 25. 16:00

Class

클래스

- C++에서 사용자 정의 데이터형*을 Abstraction(추상화)** 기법을 통해 정의할 수 있게하는 수단이다.
- 클래스는 Data Member(데이터)와 Member Function(Method; 데이터를 조작하는 수단)로 구성된다.
- 메서드 없이 데이터만 존재하는 구조체와는 대조적이며, 이 때문에 구조체를 Plain-Old Data 구조(POD 구조)라고 부르기도 한다.
- 데이터와 메서드를 구분지음으로써 Data Capsulation(데이터 캡슐화)***을 구현한다.

* Specifying Basic Type (기본형의 서술)

- 데이터의 기본형을 서술하기 위해서는 아래 3가지를 결정해야 한다.
- Built-in Data Type(내장된 기본 데이터형)의 경우, 아래와 같은 정보들이 컴파일러에 내장되어 있다.

1. 데이터 객체에 필요한 메모리의 크기
2. 메모리에 있는 이진 데이터를 해석할 방법
(\(\texttt{long}\)과 (\(\texttt{float}\)은 차지하는 메로리의 크기가 같지만, 해석되는 방식이 다르다.)
3. 데이터 객체를 이용하여 수행할 연산이나 메서드

** Abstraction (추상화)

- 정보를 UI(User Interface; 사용자 인터페이스)로 표현하는 것을 일컫는 용어이다.
- 어떤 문제에 대해 세부적인 구현 사항은 단순하게 표현함으로써 문제 해결 과정을 보다 더 거시적으로 표현하는 방법이다.
- 이러한 점에서 OOP 프로그램 설계를 Client-Server Model로 자주 비유하곤 하는데,
UI를 통해 클래스를 사용하는 프로그램이 Client에 해당되고, 클래스 선언이 Server에 해당된다.
(클라이언트는 인터페이스를 이해야하고, 서버는 클래스가 인터페이스에 따라 신뢰성 있고 정확하게 수행되는 지를 점검해야 한다.)

*** Encapsulation (캡슐화)

- 추상화된 인터페이스와 세부적 구현을 따로 결합하여 분리시키는 것이다.
- 개발이 완료된 프로그램내에서 클래스의 데이터 멤버나 메서드에 대한 수정사항이 생겼을 경우, 개발자가 인터페이스를 변경하지 않고 세부 구현 사항만 수정 가능하게 한다.


Class Declaration & Class Definition (클래스 선언 & 클래스 정의)

- 클래스 선언부와 클래스 정의부는 각각 헤더파일과 소스 코드 파일에 독립적으로 배치되는 것이 권장되는데, 이는 데이터 캡슐화의 한 예이다.


Class Declaration (클래스 선언)
- 데이터 멤버, \(\texttt{public}\) 인터페이스*, 멤버 함수(Method)가 서술되어 있는 부분이다.
- 클래스에서 어떤 데이터 멤버(변수)를 포함하고 있는지, 어떤 멤버 함수가 있는지에 대한 Function Prototype만을 표시하고 있는 부분이다.
- 짧은 길이의 멤버 함수를 제외하고, 멤버 함수의 세부적인 구현사항은 클래스 선언부가 아닌, 클래스 정의부에서 따로 표기한다.
- 헤더 파일(.h 확장자)에 작성된다.
- 클래스 이름의 첫 문자는 대문자로 표기하는 것이 일반적인 관행이다.
- 데이터 멤버는 \(\texttt{m_}\) 접두어를 사용하여, 그 변수가 데이터 멤버임을 명시해주는 것이 바람직하다.
- 클래스 선언부에서 멤버 함수의 내용을 구현할 수 있는데, 이는 자동으로 인라인 함수로 정의된다.**

* Interface (인터페이스)
- 두 시스템 간의 상호 작용을 위한 공통된 Framework이다.
- 프로그램 인터페이스는 사용자의 의도(입력하는 데이터 등)를 컴퓨터에 저장되어 있는 특정 정보로 변환한다.

// Ex. 클래스 선언 예시

// stock.h 파일 내부
#ifndef STOCK_H_
#define STOCK_H_

#include <string>

class Stock {    // class 키워드
private:
    std::string m_company;     // data member임을 명시하는 접두어 "m_"
    long m_shares;
    double m_share_val;
    double m_total_val;
    void set_tot() {total_val = shares * share_val; }  // 비교적 짧은 내용은 선언부에서 정의한다. (이는 자동으로 인라인 함수로 정의된다.)
public:
    void acquire (const std::string&, long, double);
    void buy (long, double);
    void sell(long, double);
    void update (double);
    void show();
}; // 클래스 선언부 마지막 괄호에는 세미콜론이 붙는다.

#endif

+ \(\texttt{#ifndef}\) 문법 참조

** Inline Method (인라인 멤버 함수)
- 함수 길이가 짧아, 클래스 선언부에 바로 내용을 정의할 경우, 자동으로 인라인 함수로 정의된다.
- 물론, 클래스 선언부에는 일반적으로 함수 원형을 정의하고, 클래스 메서드 정의 부분에서 인라인으로 따로 정의할 수도 있다.
(이 때, \(\texttt{inline}\) 키워드는 함수 원형부분에선 쓰지 않고, 클래스 정의 부분에서만 사용한다.)
+ Rewrite Rule(코드 수정 규칙)에 따라, 클래스 선언 내에 메서드를 정의하면, 컴파일러는 메서드의 정의를 원형으로 대체하고, 클래스 선언이 끝나는 바로 다음 부분에 인라인 함수 정의를 배치시킨다. 이는, 프로그래머에게는 보이지 않는 부분이다.

- \(\texttt{class}\) 키워드를 통해 정의된 클래스의 이름은 사용자가 데이터형의 이름(\(\texttt{int, double}\)과 같은)으로 사용할 수 있게 된다.
- 이 클래스에 정의된 내용을 토대로 클래스의 데이터 객체(Object, Instance)를 생성할 수 있다.
(흔히, 클래스를 쿠키틀, 객체를 쿠키로 비유하곤 한다.)


\(\texttt{static}\) Class Member (\(\texttt{static}\) 클래스 멤버)

- 생성되는 객체 수와 상관없이, \(\texttt{static}\) 클래스 데이터 멤버는 전 객체를 아우르는 하나만 생성된다.
- 해당 클래스의 모든 객체들의 \(\texttt{static}\) 멤버는 같은 값을 갖는다. (하나만 존재하기 때문이다.)
- \(\texttt{static}\) 멤버는 클래스 선언부 안에서 초기화될 수 없다. (클래스 초기화 부분은 해당 클래스 객체가 생성될 때 참고되는 레퍼런스에 불과하며, 메모리 또한 할당되지 않기 때문이다.)
- \(\texttt{static}\) 멤버는 클래스 선언 바깥에서 별개의 구문을 사용해서 독립적으로 초기화해야 하며, \(\texttt{static}\) 키워드는 이미 클래스 선언부의 멤버 원형 부분에 명시되어 있기 때문에, 초기화부분에서는 명시하지 않아도 된다.
- \(\texttt{static}\) 멤버는 또한 객체의 일부가 아니라 별도로 저장되는 멤버이기 때문에 멤버 함수 내에서 초기화될 수도 없다. 즉, 클래스 선언부와 클래스 메서드 정의부와 독립적인 공간에서 \(\texttt{::}\) 연산자를 통해 초기화할 수 있다.
+ 단, \(\texttt{const}\) 타입의 \(\texttt{int}\)형 \(\texttt{static}\) 멤버, 또는 \(\texttt{enum}\)형(열거체) \(\texttt{static}\) 멤버에 한하여, 클래스 선언부에서 초기화를 허용한다.

// Ex. static 클래스 데이터 멤버 초기화 예시

class Stock {
private:
    static double KOSPI;       // 클래스 선언부에서는 원형만 제공하고, 초기화는 할 수 없다.
    ...
};

double Stock::KOSPI = 1987.01;    // 초기화 구문에서는 static 키워드를 쓸 필요가 없다.
// 또한, 메서드와는 독립된 공간에서 초기화 해야 한다.

Class Definition (클래스 (메서드) 정의)

- 클래스에 포함된 멤버 함수가 세부적으로 어떻게 작동되는지를 서술하는 부분이다.
- 일반적인 Function Definition 부분과 비슷하지만 차이가 있다.*
- 멤버 함수들을 정의하기 위한 소스 코드 파일(.cpp 확장자)에 작성된다. (보통, \(\texttt{main()}\)이 있는 파일과는 별도로 구분짓는 편이다.)
- 멤버 함수는 Class Scope(클래스 사용 범위)를 갖는다.
(이 때문에, 같은 클래스의 다른 멤버 함수에서는 \(\texttt{::}\) 연산자 없이, 함수 이름만으로 접근이 가능하다.)**
- 멤버 함수 괄호 끝에 \(\texttt{const}\) 키워드를 붙이는 것은 해당 함수가 호출 객체를 수정하지 않음을 보장하는 표현이다.

* 일반함수 정의와 멤버함수 정의의 차이
- 멤버 함수는 정의될 때, 속한 클래스를 \(\texttt{::}\)연산자를 통해 명시해야 한다.
- 일반함수와 달리 멤버함수는 해당 클래스 객체의 \(\texttt{private}\) 레벨의 멤버에도 접근할 수 있다.

// Ex. 클래스 멤버 함수(메서드) 정의 예시

void Stock::update (double price) {
    ...
}

** Qualified Name(정식 표기), Unqualified Name(약식 표기)

//Ex. 정식 표기와 약식 표기 예시
Stock::update();    // Qualified Name (정식 표기)
update();           // Unqualified Name (약식 표기) : 해당 Class Scope 내에서만 사용 가능한 표기

*** \(\texttt{const}\) 멤버 함수

// Ex. const 멤버 함수 원형 및 정의 예시

void show() const;                // 함수 원형
...
void Stock::show() const {...}    // 함수 정의
// show() 멤버 함수는 해당 객체의 데이터를 수정하지 않는 메서드이다.

 


Access Control Level (접근 제어 레벨)

- 클래스 멤버(데이터와 메서드)마다의 접근 단계를 지정할 수 있다.
- 멤버 당 하나의 접근 단계만 지정할 수 있다.
- 접근할 수 있는 범위가 넓은 순서대로 \(\texttt{public, protected, private}\) 세 단계로 구성된다.
- 멤버에 접근할 때는 데이터 Objefct의 경우 멤버 연산자 \(\texttt{.}\)를 사용하고,
포인터 Obejct의 경우 포인터 멤버 연산자 \(\texttt{->}\)를 사용하여 접근한다.
- 파생 클래스의 메서드에서는 기초 클래스의 \(\texttt{public}\)과 \(\texttt{protected}\) 메서드를 호출하기 위해 \(\texttt{::}\) 연산자를 사용할 수 있다.


\(\texttt{public}\) Level
- 모든 영역에서 접근할 수 있는 단계이다.
- 프로그램(\(\texttt{main()}\) 함수)이 클래스 멤버에 접근할 수 있는 유일한 레벨이다.
- \(\texttt{public}\) 멤버 함수는 \(\texttt{private}\) 레벨의 멤버와 프로그램을 잇는 역할(인터페이스 역할)을 한다. (이와 같이, 프로그램이 데이터에 직접적인 접근을 막는 것을 Data Hiding(데이터 은닉)*이라고 한다.)
- 구조체의 경우, 접근 지정 레벨의 Default는 \(\texttt{public}\)이다.

* Data Hiding(데이터 은닉)
- 데이터의 Integrity(무결성)을 지켜주는 수단이며, 데이터 캡슐화의 한 예이다.
- 데이터에 대한 직접적인 접근을 차단함과 동시에, 데이터의 상세한 표현을 사용자가 몰라도 무방하도록 해준다.


\(\texttt{protected}\) Level
- 상속 관계의 클래스 간 접근만 허용하는 단계이다.
- 파생 클래스에서는 기초 클래스의 \(\texttt{protected}\) 멤버에 직접 접근이 가능하다. (\(\texttt{public}\)에도 접근이 가능한 것은 당연하다.)
- 외부 함수에서는 \(\texttt{public}\) 메서드를 통해 \(\texttt{protected}\) 멤버에 접근할 수 있다.
- \(\texttt{protected}\) 레벨의 애매한 입지(접근을 부분적으로만 허용하는 점) 때문에 설계상의 결점을 야기할 수 있다는 문제가 있다. 따라서 \(\texttt{protected}\) 접근 제어 지정자보다는 가급적, \(\texttt{private}\) 지정자를 사용하고, 파생 클래스는 기초 클래스의 메서드를 통해 기초 클래스의 \(\texttt{private}\) 멤버에 접근하도록 유도해야 한다.
(상황에 따라, 적절히 사용하는 것이 최고의 방법일 것이다.)


\(\texttt{private}\) Level
- 데이터를 은닉하기 위해서 일반적인 데이터 멤버들이 정의되는 단계이다.
- 접근 지정 레벨의 Default 값이다. (따로 명시하지 않을 경우, \(\texttt{private}\)으로 지정된다.)
- 메서드 또한 이 단계에서 정의될 수 있는데, \(\texttt{private}\) 레벨의 메서드는 보통 \(\texttt{public}\) 인터페이스를 구성하지 않는 세부적인 구현을 처리하는 메서드들이다.
- \(\texttt{private}\) 레벨에 Grant된 \(\texttt{friend}\) 함수가 있다면, 프로그램은 이를 통해 접근할 수 있게 된다.


Data Object (데이터 객체)

- 클래스를 통해 만들어진 하나의 객체를 의미한다.
- 클래스에서 정의된 데이터 멤버를 가지며, 그에 대한 메서드도 사용할 수 있다.
- C++은 \(\texttt{class}\)를 통해 만들어진 객체가 \(\texttt{int}\)나 \(\texttt{char}\)와 같은 Built-in 데이터형과 사용감에 있어서 큰 차이 없이 자연스러울것을 지향한다.
- 객체는 함수의 매개변수나 리턴값으로 사용 가능하고, \(\texttt{new}\) 연산자를 사용할 수 있으며 대입 또한 가능하다. (별도의 정의가 필요한 경우도 있다,)

* Array of Object (객체 배열)
- 데이터 객체 또한 배열로 구성될 수 있으며, 그 문법 또한 다르지 않다.

Stock stock3[3] = {
    Stock("NanoSmart", 20, 12.5),
    Stock("BoffoObjects", 200, 2.0),
    Stock("MonolithicObelisks", 130, 3.25)
};

ADT (Abstraction Data Type; 추상화 데이터형)

- 프로그래밍 언어나 시스템의 세부적인 것들은 논외로 간주하고, 데이터형을 일반적인 형식으로 서술하는 방법이다.
- C++의 \(\texttt{class}\) 개념이 ADT를 구현할 수 있는 좋은 방법 중 하나이다.
- \(\texttt{private}\) 부분에는 데이터 저장 방법을 정의하고, \(\texttt{public}\) 부분에는 인터페이스를 정의한다.


Member Function Properties (멤버 함수들의 종류와 특성)

C++ Primer Plus 6E pp.777-778. 발췌


Nested Enumeration, Structures and Classes (내포된 열거체, 구조체와 클래스)

- 열거체, 구조체, 클래스가 특정 클래스 내부에서 정의되면, "해당 클래스에 내포되었다."라고 표현한다.
- 클래스 내에 선언(정의)된 열거체, 구조체, 클래스의 Scope는 해당 클래스의 범위이다.
- private 멤버로 선언될 경우, 해당 구조는 해당 클래스 내에서만 사용할 수 있다.
- public 멤버로 선언될 경우, 해당 구조는 사용 범위 결정 연산자("::")를 통해 클래스 외부에서도 사용할 수 있다.

※ Nested 구조를 지원하지 않는 컴파일러도 존재한다.