C++ Class Basics
The Simplest C++ Class Example
class MyClass {
public:
void readValue() {
std::cout << "Value: " << value << std::endl;
}
private:
int value;
};
가장 간단한 형태의 클래스 모습입니다. C에서 C++로 넘어오면서 생긴 가장 큰 변화 중 하나는 클래스라는 새로운 데이터 타입입니다. 기존의 C언어에서는 구조체와 함수를 사용해서 시스템을 설명했습니다. 그러나 C++은 변수들과 함수들을 묶어서 표현할 수 있는 클래스를 제공합니다. 위의 코드를 보면 MyClass
가 함수와 변수를 가지고 있습니다. 구조체가 함수를 가지고 있는 것 처럼 보입니다. 구조체가 동작을 설명하는 함수도 가질 수 있는 것이죠.
이 때 클래스 가지고 있는 변수와 함수를 멤버 변수와 멤버 함수라고 말합니다(alias, enum, class 등도 가지고 있을 수 있습니다). public
으로 선언된 멤버들을 클래스의 인터페이스를 제공하고, priavte
으로 선언된 멤버들은 세부 구현을 제공합니다.
위 예제에서는 선언과 구현을 함께 기술했는데요, 아래처럼 헤더파일에서는 선언하고 소스 파일에서 구현하는 것도 가능합니다.
// header file
class MyClass {
public:
void readValue();
private:
int value;
};
// source file
MyClass::readValue() {
std::cout << "Value: " << value << std::endl;
}
Member Access
class MyClass의 멤버는 . (dot)으로 접근할 수 있으며 MyClass 객체의 포인터의 멤버에는 -> (arror)를 사용해서 접근할 수 있습니다.
struct MyClass {
void readValue();
int value;
};
void user(MyClass myClass, MyClass& pMyClass) {
myClass.value = 1;
myClass.readValue();
pMyClass->value = 2;
myClass->readValue();
}
위에서 class
키워드 대신에 struct
를 사용했는데요, C++에서 struct
는 기본적으로 멤버들이 public이라는 점을 제외하면 class와 동일합니다. class는 별다른 접근 지정자가 없다면 멤버들은 private입니다.
Access Control
객체 외부에서는 인터페이스(public
)만 접근 가능하고, 세부 구현(private
)에는 접근할 수 없습니다. 여기서 public과 private은 C++에서 접근 지정자(access specifiers)라고 합니다.
class MyClass {
public:
void readValue() {
std::cout << "Value: " << value << std::endl;
}
private:
int value;
};
int main() {
MyClass myClass;
myClass.value = 7; // error: MyClass::value is private
myClass.readValue();
return 0;
}
이렇게 인터페이스와 구현을 분리하면 여러가지 장점이 있습니다.
- 다른 사람이 만들어놓은 클래스를 사용하려고 할 때, 해당 클래스의 전체 코드를 파악할 필요 없이 인터페이스만 확인해도 되므로 다른 클래스들을 사용하기 쉽습니다.
- 멤버 변수에 버그가 있다면 이것은 반드시 멤버 함수에 의해 발생된 버그입니다. 버그가 발생하는 위치를 전체 코드에서 찾아갈 필요없이 멤버 함수에서부터 디버깅을 하면 되므로 디버깅이 쉽습니다.
- 구현을 바꾸더라도 인터페이스가 동일하므로 나머지 코드는 신경 쓸 필요 없이 클래스만 수정하는 것이 가능합니다.
따라서 초기 value를 설정할 수 있도록 클래스를 고쳐보면 아래와 같습니다.
class MyClass {
public:
void init(int input) {
value = input;
}
void readValue() {
std::cout << "Value: " << value << std::endl;
}
private:
int value;
};
int main() {
MyClass myClass;
myClass.init(7);
myClass.readValue();
}
Constructors
객체를 선언하면서 초기 속성 값들을 설정해야 하는 것은 당연합니다. 모든 객체들은 구체적인 값을 가지고 있습니다. 자동차 객체는 만들어지면서 특정한 색으로 만들어집니다. 색이 정해지지 않은 자동차 객체는 없습니다. 책상이라는 객체도 만들어지면서 구체적인 크기로 만들어집니다. 크기가 정해지지 않은 책상 객체는 없습니다. 따라서 초기값을 설정하는 함수가 필요합니다. 위에서는 init()
함수가 초기값을 설정하는 역할을 했습니다.
그러나 init()
과 같은 함수를 사용해 속성을 초기화하는 것은 실수가 일어날 가능성이 있습니다. 프로그래머가 초기화 함수를 호출하는 것을 까먹거나 여러번 호출할 수 있기 때문입니다. 이러한 실수를 방지하기 위해 객체를 초기화하는 함수를 C++ 문법에서 명시적으로 선언할 수 있도록 제공하고 있고, 이러한 함수를 생성자(Constructor)라고 합니다.
클래스 이름과 동일한 이름의 함수는 생성자로 사용됩니다. 생성자를 가지고 있는 클래스의 모든 객체들은 선언될 때 생성자를 호출하고, 생성자를 통해 초기화됩니다.
class MyClass {
public:
MyClass(int input): value{input} {} // constructor
void readValue() {
std::cout << "Value: " << value << std::endl;
}
private:
int value;
};
int main() {
MyClass myClass{7};
myClass.readValue();
return 0;
}
생성자에 : value{input}
이라는 코드를 사용했는데요, C++11 이상에서는 초기값을 설정할 때 이렇게 initialization list와 uniform initializer를 사용해서 초기값을 설정할 수도 있습니다. 물론 아래와 같이 코딩하는 것도 가능합니다.
...
MyClass(int input) {
value = input;
}
...
Destructors
생성자와는 반대되는 소멸자도 있습니다. 객체가 사라질 때 호출되는 함수입니다. 예를 들어 함수 내에서 선언된 객체는 함수 실행이 끝나고 리턴될 때 사라지며, 이 때 소멸자가 있는 경우 호출됩니다. 아래와 같이 클래스 이름 앞에 ~
기호를 붙여 소멸자를 선언할 수 있습니다.
class MyClass {
public:
// ...
~MyClass() {
std::cout << "Destructor" << std::endl;
}
// ...
};
예를 들어 생성자에서 동적으로 메모리를 할당한 경우 소멸자에서 할당한 메모리를 해제하도록 하면 메모리 관리가 편해집니다.
Superclass and Subclass
슈퍼클래스를 상속받아 서브 클래스를 만들 수도 있습니다. 아래는 Animal
클래스를 상속받아 Dog
클래스를 만드는 예제입니다.
// Superclass (Base class)
class Animal {
public:
Animal(int age) : age{age} {}
void showAge() {
cout << "age: " << age << endl;
}
private:
int age;
};
// Subclass (Derived class)
class Dog : public Animal {
public:
Dog(string name, int age) : Animal{age}, name{name} {}
void bark() {
cout << name << " is barking!" << endl;
}
private:
string name;
};
int main() {
Dog myDog("Buddy", 3);
myDog.bark();
myDog.showInfo();
return 0;
}
서브클래스는 슈퍼클래스의 public, protected 멤버에 접근할 수 있습니다.
Leave a comment