클래스란 무엇인가
클래스는 데이터(멤버 변수)와 동작(멤버 함수)을 하나의 단위로 묶는 사용자 정의 타입의 설계도입니다. int 나 double 같은 기본 타입이 단일 값을 표현하는 반면, 클래스를 사용하면 은행 계좌, 2D 점, 플레이어 같은 하나의 개념 전체를 이리저리 전달할 수 있는 단일 값으로 모델링할 수 있습니다.
당신은 이미 클래스를 직접 정의하지 않고도 사용해 왔습니다. std::string 과 std::vector 는 표준 라이브러리의 클래스입니다. name.length() 나 v.push_back(3) 을 호출하는 것은 객체에 대해 멤버 함수를 호출하는 것입니다. 이제 같은 방식으로 자신만의 타입을 만들고, 그다음에는 그것들을 초기화하기 위해 생성자로 넘어갑니다.
클래스 정의하기와 객체 생성하기
클래스 정의는 중괄호 안에 멤버를 나열하고 세미콜론으로 끝납니다. 그 끝의 ; 를 잊는 것은 초보자가 가장 흔히 저지르는 실수 중 하나입니다. 클래스로부터 생성하는 각 객체는 멤버 변수의 자체 복사본을 갖습니다.
rex 와 luna 는 서로 다른 두 객체입니다. rex.name 을 바꿔도 luna.name 에는 영향이 없습니다. 각 객체는 자신의 데이터를 보관합니다. 멤버에는 객체에 대해 점 연산자 . 로 접근합니다(객체를 가리키는 포인터가 있을 때는 ->).
public 대 private
기본적으로 class 안의 모든 것은 private 입니다. 클래스 자신의 멤버 함수만이 그것에 접근할 수 있습니다. public: 라벨은 멤버를 외부 코드에 공개합니다. 이 구분이 캡슐화의 핵심입니다. 안전한 인터페이스를 노출하고 내부 데이터를 숨겨서, 데이터가 잘못된 상태에 빠지지 않도록 합니다.
count 가 private 이므로 어떤 호출자도 음수나 말이 안 되는 값을 몰래 넣을 수 없습니다. 반드시 increment() 를 거쳐야 합니다. value() 의 매개변수 목록 뒤에 있는 const 는 그 함수가 객체를 변경하지 않겠다고 약속합니다. 덕분에 const 객체에 대해서도 호출할 수 있고, 읽는 사람에게 의도를 알려 줍니다. public, private, protected 에 대한 전체 이야기는 접근 지정자를 참고하세요.
메서드의 선언과 정의
작은 클래스에서는 (위처럼) 메서드를 인라인으로 정의해도 괜찮습니다. 큰 클래스에서는 메서드를 클래스 안에서 선언하고, ClassName::method 라는 범위 지정 문법을 사용해 본문은 클래스 밖에서 정의하는 것이 일반적입니다. 이렇게 하면 클래스 정의가 인터페이스 요약처럼 읽기 쉽게 유지됩니다.
Rectangle:: 접두사는 그 함수가 어느 클래스에 속하는지 컴파일러에게 알려 줍니다. 그런 정의 안에서도 멤버는 여전히 그냥 이름(width, area())으로 참조합니다. 컴파일러는 그것들이 메서드가 호출된 대상 객체에 속한다는 것을 알고 있습니다.
this 포인터
정적이 아닌 모든 멤버 함수 안에서 this 는 그 함수가 호출된 대상 객체를 가리키는 포인터입니다. 보통은 필요 없습니다. 그냥 멤버 이름만으로도 이미 현재 객체를 가리키기 때문입니다. 매개변수가 멤버를 가린다(shadow) - 즉 같은 이름을 공유한다 - 그래서 모호함을 없애야 할 때 진가를 발휘합니다.
this-> 없이 setX 안에서 x = x; 라고 쓰면 매개변수를 자기 자신에게 대입할 뿐 멤버는 그대로 남습니다. 조용한 버그입니다. this->x 는 이를 명확하게 만듭니다. 많은 코드베이스는 매개변수의 이름을 다르게 지어(예: setX(int newX)) 이 문제를 아예 피하지만, 실제 코드에서는 this-> 를 끊임없이 보게 될 것입니다.
흔한 실수
몇 가지 클래스 함정은 사람들을 반복해서 붙잡습니다.
- 닫는 세미콜론을 빠뜨리기. 클래스 정의는
};로 끝납니다.;를 빠뜨리면 클래스 자체가 아니라 클래스 뒤에 오는 것을 가리키는 혼란스러운 오류가 쏟아집니다. - 멤버 초기화를 잊기.
int와double같은 기본 타입 멤버는 자동으로 0 이 되지 않습니다. 초기화되지 않은 멤버를 읽는 것은 미정의 동작입니다. 멤버에 기본값을 주거나(int count = 0;) 생성자에서 초기화하세요. - 외부에서 private 멤버에 접근하기. private 멤버에 대한
c.count = 5;는 컴파일 오류입니다. 대신 public 메서드를 거치세요. 이것은 캡슐화가 의도대로 작동하는 것입니다. - 클래스를 객체와 혼동하기.
Dog.bark();는 잘못입니다.Dog는 타입입니다. 메서드는 객체에 대해 호출합니다:rex.bark();.
// 초기화되지 않은 멤버 - 'age' 를 읽는 것은 미정의 동작:
class Cat {
public:
int age; // 기본값 없음
};
Cat c;
std::cout << c.age; // 0 이 아니라 쓰레기 값
다음: 생성자
객체를 생성한 뒤 각 멤버를 손으로 설정하는 것은 - rex.name = ...; rex.age = ...; - 번거롭고 빠뜨리기 쉬우며, 바로 그렇게 해서 초기화되지 않은 멤버가 생깁니다. 다음 페이지에서는 생성자를 다룹니다. 객체가 생성될 때 자동으로 실행되는 특별한 함수로, Dog rex("Rex", 4); 라는 깔끔한 한 줄 문법으로 모든 객체가 유효한 상태에서 시작하도록 보장할 수 있습니다.
자주 묻는 질문
C++ 에서 클래스와 객체의 차이는 무엇인가요?
클래스는 설계도입니다. 타입이 어떤 데이터(멤버 변수)와 어떤 동작(멤버 함수)을 갖는지 기술합니다. 객체는 그 설계도로부터 만들어진 구체적인 인스턴스입니다. class Dog { ... }; 는 타입을 한 번 정의하고, Dog rex; 는 실제로 사용할 수 있는 Dog 를 생성합니다. 하나의 클래스로 서로 독립적인 여러 객체를 만들 수 있습니다.
C++ 에서 class 와 struct 의 차이는 무엇인가요?
기술적으로는 기본 접근 수준만 다릅니다. class 의 멤버는 기본적으로 private, struct 의 멤버는 기본적으로 public 입니다. 둘 다 멤버 함수, 생성자, 상속을 가질 수 있습니다. 관례적으로 struct 는 단순한 데이터 묶음에, class 는 동작과 지켜야 할 불변식을 가진 타입에 사용합니다.
C++ 클래스에서 this 포인터는 무엇을 하나요?
멤버 함수 안에서 this 는 그 함수가 호출된 대상 객체를 가리키는 포인터입니다. 매개변수와 멤버를 구분하거나(this->x = x;) 현재 객체를 반환할 때 사용합니다. 일반적인 멤버 접근에는 거의 필요 없습니다. 그냥 x 라고 쓰면 이미 현재 객체의 x 를 가리킵니다.