나만의 작은 도서관
[C++][Class] 클래스(Class) 본문
클래스(Class)란?
- 클래스는 "특정 객체를 생성하기 위해 변수나 메서드를 정의하는 틀(템플릿)"을 의미한다.
- 클래스(또는 구조체) 내부에 포함된 요소들을 “멤버”라고 부르며, 멤버는 변수의 타입에 따라 두 가지 멤버 접근 연산자(‘ . ’, ‘ → ’)를 통해 접근한다.
- 일반 타입이라면 ‘ . ’, 포인터 타입이라면 ‘ → ’를 사용해 멤버에 접근한다.
MyClass myClass;
MyClass* myClass2 = new MyClass();
myClass.data; // 일반 타입 변수는 '.'을 통해 접근
myClass2->data; // 포인터 타입 변수는 '->'를 통해 역참조 접근
클래스의 선언부와 구현부
- C++에서 클래스는 보통 클래스 선언부와 클래스 구현부로 나눠 관리한다. 각각은 다음과 같다.
- 클래스 선언부(class declaration)
- 클래스의 구조를 정의. 헤더 파일(.h)에 작성한다.
- 클래스와 멤버를 선언하며, 멤버는 접근 지정자(private 등)를 지정해 접근을 제한할 수 있다.
- 클래스 구현부(class implementation)
- 클래스의 멤버 함수들을 구현. 소스 파일(.cpp)에 작성한다.
- 클래스 선언부를 #include로 가져온 다음, 선언한 멤버 함수를 정의하는 것이 전형적인 패턴이다.
- 멤버 함수 정의 시 클래스의 멤버 함수임을 명시하기 위해 범위 지정 연산자::를 사용하며, ClassName::func()와 같은 형식으로 기술한다.
- 클래스 선언부(class declaration)
선언부과 구현부로 분리하지 않은 경우
- 선언부와 구현부를 분리하여 작성하는 것이 가독성이 높고 관리가 용이하기 때문에 권장된다. 하지만 구현부 코드가 너무 짧거나, 파일의 개수를 늘리고 싶지 않은 경우 일부 또는 전체를 통합하여 관리하기도 한다.
- 다만, 클래스의 선언부와 구현부를 통합하여 작성했을 경우 컴파일러가 멤버 함수를 인라인 함수로 변경할 수도 있다는 점을 유의해야 한다.
인라인(inline) 함수란?
함수 호출 부분이 전부 함수 코드로 대체되는 함수를 말한다. 컴파일러가 자동으로 선언하며, 이 때문에 사용자가 직접 선언하지 않아도 된다.
클래스의 주요 구성 요소
- 멤버 변수(Member Variables)
- 멤버 함수(Member Functions)
- 생성자(Constructor)
- 소멸자(Destructor)
- 멤버 접근 지정자 (Member Access Specifiers)
멤버 변수(Member Variables, 또는 필드)
- 객체의 상태(데이터)를 저장하는 변수
- private으로 선언하여 캡슐화하는 것이 일반적이다.
- C++11에 기본 멤버 초기화(default member initialization, 다른 말로 in-line initialization)가 생기면서 멤버 변수는 클래스 내에서 기본값을 들고 있을 수 있다.
- 기본값은 생성자에서 값을 변경하면 변경된 값으로 덮어씌워진다.
class ClassName
{
int var1; // 데이터 저장
int var2 = 10; // 기본 멤버 초기화
}
- C++에서 구조체와 클래스는 똑같기 때문에(기본 접근 제어자가 다른 걸 제외하면), 구조체처럼 멤버 변수는 선언한 순서대로 메모리가 배치되며, 메모리 정렬 규칙에 의해 패딩(padding)이 낄 수 있다. (이에 대한 자세한 내용은 여기를 참고)
멤버 함수 (Member Functions)
- 객체의 동작(기능)을 정의하는 함수
- 멤버 변수를 조작하거나 특정 기능을 수행한다.
- public으로 선언하여 외부에서 접근 가능하도록 하는 것이 일반적이다.
class ClassName
{
void setData(int value);
int getData() const; // 상수 함수
}
멤버 접근 지정자 (Member Access Specifiers)
- 클래스 멤버(변수, 함수)에 대한 접근 범위를 지정
- 클래스의 기본 접근 지정자는 private이다.
- 같은 지정자를 여러 번 사용할 수 있다.
- 주요 접근 지정 지정자는 아래와 같다.
- public: 외부에서 접근 가능
- protected: 자식 클래스에서 접근 가능
- private: 현재 클래스 내부에서만 접근 가능
class ClassName {
/*getter-setter 멤버 함수(public)*/
public:
void setData(int value);
int getData() const;
/*계산 관련 멤버 함수(public)*/
public:
int add2();
int Sub2();
/*멤버 변수(private)*/
private:
int data; // 데이터 저장
};
생성자(constructor)
- 객체가 생성될 때 자동으로 호출되는 함수. 객체의 초기화를 담당한다.
- 클래스는 여러 개의 생성자를 정의하는, 즉, 생성자 오버로딩이 가능하다. 생성자 오버로딩은 1) 다양한 타입의 인자를 받는 상황을 정의하거나, 2) 복사 생성자, 대입 생성자 등 다양한 종류의 생성자를 정의한다. (자세한 내용은 여기를 참고)
생성자 정의 및 호출
- 생성자는 객체를 초기화하는 역할을 하기 때문에 리턴값이 없다.
class ClassName
{
// 생성자 정의
ClassName (T1 arg1, T2 arg2, ...) {}
};
int main()
{
// 생성자 호출
ClassName obj(arg1, arg2, ...); // 정적 할당 방식
ClassName obj = new ClassName(arg1, arg2, ...); // 동적 할당 방식
}
생성자 호출 시 주의할 점
- 객체를 정적 할당하는 방식으로 선언할 때, 인자가 없는 생성자를 호출하기 위해 “A a()”처럼 적으면 안 된다. 이는 객체 선언이 아닌 “매개변수가 없고, A 타입을 리턴하는 함수 a”를 선언하는 문장이 되므로, “A a”처럼 적어야 한다.
A a(); // int func(); 와 같은 함수 선언 형태. (X)
A a; // 객체 선언 (O)
기본 생성자(default constructor)
- 매개변수와 내용이 없는 생성자.
- 기본 생성자는 클래스 내에 생성자를 따로 정의하지 않았을 때 컴파일러가 자동으로 추가한다.
- 반대로, 사용자가 생성자를 1개라도 명시적으로 정의할 경우 기본 생성자는 추가되지 않는다.
- 만약 “인자가 없는 생성자는 기본 생성자를 사용하겠다”는 의도를 명시하고 싶다면 '= default'를 사용하면 된다. (C++11)
- 반대로, “인자가 없는 생성자는 기본 생성자를 사용하지 않겠다”는 의도를 명시하고 싶다면 '= delete'를 사용하면 된다. (C++11)
class ClassName
{
public:
// 생성자를 정의하지 않으면 기본 생성자인 ClassName() {}를 자동 생성
ClassName() {} // 명시적으로 기본 형식의 생성자 정의
ClassName() = default; // 컴파일러가 제공하는 기본 생성자를 사용. (C++11~)
ClassName() = delete; // 컴파일러가 제공하는 기본 생성자를 사용 안 함. (C++11~)
};
생성자의 초기화 리스트(initializer list)
- 초기화 리스트는 생성자 호출과 동시에 멤버 변수들을 초기화한다.
- 초기화 리스트를 사용하면 코드를 보다 간결하게 작성할 수 있다.
생성자 초기화 리스트 정의
- 생성자로 넘겨받은 인자의 사용 여부나 대입 순서는 상관없다.
// varX는 멤버 변수, argX는 인자
ClassName(T1 arg1, T2 arg2, ...) : var1(arg1), var2(arg2), ... {} // 클래스 내부 정의 방식
ClassName::ClassName(T1 arg1, T2 arg2, ...) : var1(arg1), var2(arg2), ... {} // 클래스 외부 정의 방식
생성자 초기화 리스트 예제
- 아래 두 코드의 결과는 같다.
// 생성자 내부에서 초기화
Marine::Marine() {
hp = 50;
coord_x = coord_y = 0;
damage = 5;
is_dead = false;
}
// 생성자 초기화 리스트 사용
Marine::Marine() : hp(50), coord_x(0), coord_y(0), damage(5), is_dead(false) {}
초기화 리스트의 성능적 이점
- 생성자 내부에서 초기화하는 방식은 기본 생성자가 호출한 다음 대입 연산을 해야 초기화된다. 즉, 2번의 연산을 걸쳐서 초기화한다.
- 반면 초기화 리스트는 객체가 생성될 때 초기화 연산 한 번으로 초기화되기 때문에 생성자 내부에서 초기화하는 방식보다 효율적이고 성능이 좋다.
class MyClass{
private:
int _a;
public:
// 초기화 리스트 사용. int _a = a; 와 동일
MyClass(int a) : _a(a) {}
// 생성자 내부에서 초기화. int _a; _a = a;와 동일
MyClass(int a) { x = a; }
};
초기화 리스트를 사용해야 하는 경우
- 상속 관계(is-a)인 기반 클래스의 특정 생성자를 명시적으로 호출해야 하는 경우
class Derived: public Base
{
public:
Derived(int x) : Base(x) {} // 초기화 리스트에서 기반 클래스 생성자 명시적 호출
}
- 포함 관계(has-a)인 멤버 클래스의 특정 생성자를 명시적으로 호출해야 하는 경우
class Car : public Engine
{
public:
Car(int power) : engine(power) {} // 초기화 리스트에서 멤버 클래스 생성자 명시적 호출
private:
Engine engine; // Engine 객체 포함 (has-a 관계)
}
- 멤버 변수가 반드시 정의와 초기화를 동시에 해야 하는 타입(레퍼런스, const 타입)인 경우.
class MyClass
{
public:
// 초기화 리스트로 레퍼런스(&), 상수 멤버 변수 초기화
MyClass(int& arg1, int arg2) : _refVar(arg1), _constVar(arg2) {}
private:
int& _refVar;
const int _constVar;
}
소멸자(Destructor)
- 객체가 소멸될 때 자동으로 호출되는 함수.
- (객체는 정적 할당 시 스코프를 벗어날 때, 동적 할당 시 delete 할 때 소멸된다.)
소멸자 정의
- 소멸자는 리턴값과 매개변수를 가지지 않으며, 단 하나만 존재해야 하므로 오버로딩을 할 수 없다.
ClassName()
{
~ClassName(){} // 소멸자 정의
}
소멸자를 명시적으로 정의하는 경우
- 소멸자도 생성자처럼 정의하지 않아도 자동으로 추가되기 때문에 특별한 이유가 없다면 소멸자를 정의하지 않아도 된다.
- 하지만 기본 소멸자는 해당 객체(this)에 저장된 데이터만 해제하고 데이터가 가리키는 메모리는 삭제하지 않기 때문에, 객체 소멸 시 객체 내에서 사용한 동적 메모리(리소스)까지 해제해야 한다면 소멸자를 명시적으로 정의해야 한다.
~ClassName(){
delete Resources; // 동적 메모리 할당 해제
delete Resources2; // 동적 메모리 할당 해제
...
}
// 동적 할당한 멤버 변수들
private:
T* Resources;
U* Resources2;
this 포인터
- 객체 자신을 가리키는 포인터.
- 일반적으로 this는 해당 객체의 멤버 함수 내에서 해당 객체를 가리키는 포인터를 얻기 위해 사용한다.
this의 주요 특징
- 클래스 내부에서는 모든 멤버에 대해 this→를 생략하여 사용할 수 있다. 단, 매개변수가 멤버 변수와 이름이 같은 경우 this→를 붙여 구분해야 한다.
class MyClass {
public:
int value;
void setValue(int value) {
this->value = value; // this->value는 멤버 변수, value는 매개변수
}
};
- *this는 객체 자신을 의미한다. 만약 멤버 함수에서 해당 객체를 넘겨주고 싶다면 객체 자신을 의미하는 *this를 레퍼런스를 리턴값으로 넘겨주면 된다.
class MyClass {
public:
int value;
MyClass& setValue(int v) {
this->value = v;
return *this; // 객체 자신을 반환
}
};
int main() {
MyClass obj;
obj.setValue(10).setValue(20); // 체이닝 가능
}
- this 포인터는 모든 비정적(Non-static) 멤버 함수에 자동으로 전달된다.
- 즉, 특정 객체에 속하는 비정적 멤버 함수 내부에는 this 키워드가 정의되어 있다. 반면에 클래스 자체에 속하는 static 멤버 함수 내부에는 this 키워드가 없다.
class MyClass {
public:
static void staticFunction() {
// this 사용 불가능!
// this->value = 10; // 오류 발생!
}
};
참고 자료
https://www.tcpschool.com/cpp/cpp_inheritance_derivedClass
https://dream-and-develop.tistory.com/54
https://learn.microsoft.com/ko-kr/cpp/cpp/class-member-overview?view=msvc-170
'C++ > 문법 및 메소드(STL)' 카테고리의 다른 글
[C++][Class] 연산자 오버로딩(Operator Overloading) (0) | 2025.03.25 |
---|---|
[C++][Class] 함수 / 생성자 오버로딩(overloading) (0) | 2025.03.25 |
[C++] 동적 할당 (0) | 2025.03.19 |
[C++][STL Container] 비교 연산자와 반복자 지원 (0) | 2025.03.18 |
[C++][STL Container] 컨테이너 어뎁터 (0) | 2025.03.18 |