
구조와 클래스의 차이점
C++에는 여러 레코드를 보유하는 두 가지 대표적인 방법인 구조체와 클래스가 있습니다. 둘은 비슷한 역할을 하는 것 같은데, 둘의 차이점은 무엇인가요?
액세스 수정자 – 구조는 공개, 클래스는 비공개
우선 기본 액세스 수정자가 다릅니다. 기본 액세스 한정자는 구조체 또는 클래스가 선언되고 액세스 한정자가 명시적으로 설정되지 않은 경우 기본적으로 지정되는 멤버 액세스 한정자를 참조합니다.
따라서 구조를 만들 때 액세스 한정자를 사용하지 않으면 모든 멤버가 자동으로 공용으로 처리됩니다.
반대로 클래스가 생성되면 기본적으로 비공개로 지정됩니다.
struct UserData {
int ID; // public
std::string name; // public
};
class DataManager {
UserData user; // private
std::vector<int> buy_list; // private
};
물론 권장되는 코딩 스타일 가이드는 클래스의 모든 액세스 한정자를 명시적으로 설명하는 것입니다. 즉, 기본적으로 비공개로 지정되어 있어도 명시적으로 비공개로 작성하는 것이 권장되는 스타일입니다.(구조체의 경우는 나중에 설명할 이유 때문에 약간 다릅니다. 자세한 내용은 아래에서 설명합니다.)
struct DataManager {
private: // recommended style
UserData user;
std::vector<int> buy_list;
};
사실 그 외에 다른 차이점은 없습니다.
흥미롭게도 C++에서는 액세스 수정자 외에 클래스와 구조체 간에 차이가 없습니다. 두 액세스 수정자가 명시적으로 설명되면 개인, 보호 및 공용 멤버를 가질 수 있으며 흥미롭게도 C++의 구조체도 상속될 수 있습니다. C++ 구조체는 C 구조체와 매우 다릅니다.
실제로 어셈블리 관점에서도 구조체와 클래스는 동일합니다. 둘 다 동일한 레이아웃을 가진 연속적인 데이터 블록으로 메모리에 표시됩니다.
사실, C++의 클래스는 본질적으로 구조체에 C의 함수 포인터를 더한 것입니다. C 언어에서도 클래스를 만들 수 있으며 이는 구조체에 함수 포인터를 넣는 것입니다. 기본적으로 클래스와 구조는 다르지 않습니다.
실제로 클래스와 구조체는 컴파일된 어셈블리에서 비교할 때 정확히 동일합니다.
못 믿겠으면 보여줄게
#include <string>
struct UserData1 {
int ID; // public
std::string name; // public
};
class UserData2 {
int ID; // public
std::string name; // public
};
int main()
{
UserData1 user1;
UserData2 user2;
return 0;
}
위의 코드를 어셈블리로 변환하면 다음과 같습니다.
UserData1::UserData1() (base object constructor):
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR (rbp-8), rdi
mov rax, QWORD PTR (rbp-8)
add rax, 8
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string() (complete object constructor)
nop
leave
ret
; destructor는 생략
UserData2::UserData2() (base object constructor):
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR (rbp-8), rdi
mov rax, QWORD PTR (rbp-8)
add rax, 8
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string() (complete object constructor)
nop
leave
ret
;마찬가지로 destructor 생략
위의 어셈블리 코드에서 볼 수 있듯이 UserData1과 UserData2는 완전히 동일한 코드를 가집니다. 즉, 클래스와 구조체의 구분은 단순히 컴파일 단계의 구분이며 실제 작동 원리는 완전히 동일합니다.
사실 여담으로 이 사실이 당연하단 것을 기억해야 한다. 왜냐면 **C언어에 없는 것들은 다른 언어에서도 무조건 없기 때문이다.** 애초에 C언어 자체가 기계어와 많이 가깝고, C언어 코드가 대부분 그대로 기계어로 번역되기 때문이다. 이 말이 무엇을 의미하냐면, C에서 없는 기능은 다른 언어에서도 존재하지 않는다. 기계어로 구현할 수 없다. 단지 컴파일러가 잡아낼 수 있도록 인터페이스를 구성한 것 뿐이다. 클래스 역시 C언어에 존재하지 않는다. 이 말은 클래스 역시 사실은 구조체를 변형한 인터페이스일 뿐임을 의미한다.
그러나 클래스는 데이터와 메서드 함수를 그룹화하기 때문에 구조체보다 약간의 오버헤드가 발생할 수 있습니다. 캡슐화 그리고 추상적인우리는 하는 데 집중하고 구조는 데이터를 나타냅니다실행 중심이므로 적절하게 구현하면 약간의 오버헤드가 있을 수 있습니다.
함수는 구조체 내부에서도 선언할 수 있습니다.
앞에서 언급했듯이 C++에서는 구조체 내부에 함수를 선언할 수 있습니다. 물론 나중에 설명하겠지만 구조체에 함수를 만들 이유는 없다.
struct UserData {
int ID; // public
std::string name; // public
auto ResquestUserData(const std::string& uri); // 함수 선언 가능
};
// 클래스처럼 함수 정의도 가능하다
auto UserData::ResquestUserData(const std::string& uri)
{
return;
}
C 구조와 다르다고 할 수 있지만 그 말은 반은 참이고 반은 거짓이다.
실제로 C에서도 함수 포인터를 사용하여 구조체의 멤버 함수에 항목을 넣을 수 있습니다. 그러나 C에는 액세스 수정자가 없으므로 필요하지 않습니다. C++ 클래스와 구조체는 C 구조체 + 함수 포인터 + 컴파일러 규칙을 결합한 결과이기도 합니다.
무엇을 쓸 것인가
이 부분은 기술적인 내용보다 프로그래밍 스타일에 관한 것입니다.
사실 이 부분에 대한 답은 간단하다. 일련의 데이터가 아닌 이상 클래스를 작성할 수 있습니다.
실제로 대부분의 경우 클래스를 사용할 수 있습니다. 메서드 함수, 연산자 오버로드, 생성자/소멸자 또는 전용 멤버를 입력할 때마다 클래스를 사용할 수 있습니다.
그러나 예외적으로 모든 멤버가 공개되고 멤버 함수가 없으며 단순한 레코드만 표현되는 경우 구조체가 클래스보다 더 유용할 수 있습니다.
실제로 클래스를 사용하여 거의 모든 곳에서 일관성을 유지할 수 있지만 정말 간단한 데이터 세트가 필요하고 구조가 유용할 때가 있습니다. 예를 들어 할당 연산자를 사용하여 데이터를 복사할 때 구조체에서 복사하는 과정이 마치 memcpy예를 들어
struct Asset {
int id;
double price;
}
int + double 따라서 12바이트가 있으면 복사하여 생성됩니다. memcpy12바이트만 자주 복사됨(실제로는 16바이트입니다. 그 이유는 이 글에서 간략하게 언급했지만, 나중에 더 자세히 다루겠습니다).
다만, 클래스의 경우 보통 별도의 복사 생성자를 정의하는 경우가 있는데, 이 경우 단순히 메모리 복사를 수행하는 것보다 오버헤드가 더 클 수 있다.
그러나 사실 성능상의 이유보다 코딩 스타일 가이드로 더 많이 볼 수 있다고 생각합니다. 이 규칙을 반드시 따를 필요는 없지만 클래스와 구조체를 일관되게 사용하는 것이 중요합니다. 그렇게 하면 적어도 이 구조 내에 멤버 함수가 없고 단지 데이터 컨테이너라는 것을 알 수 있습니다.
일반적으로 보편적으로 따르는 코딩 스타일이기도 합니다.
