본문으로 건너뛰기 C++ struct vs class | 접근 제어·POD·C 호환 완벽 비교

C++ struct vs class | 접근 제어·POD·C 호환 완벽 비교

C++ struct vs class | 접근 제어·POD·C 호환 완벽 비교

이 글의 핵심

C++ struct vs class 차이는 기본 public/private뿐, 기능은 동일합니다. 데이터 묶음 vs 캡슐화 관례, POD·C 호환까지 문법은 같고 의도 표현과 선택 기준을 비교합니다.

들어가며

C++에서 structclass기본 접근 제어만 다를 뿐, 기능은 완전히 동일합니다. 비유로 말씀드리면, 문법상 차이는 회의실 문 앞에 ‘공개’ 스티커를 붙이느냐, ‘비공개’가 기본이냐 정도이며, 방 안에서 할 수 있는 일(메서드, 상속 등)은 같습니다. 관례적으로는 데이터 묶음에는 struct, 불변식을 지키는 객체에는 class를 쓰는 경우가 많습니다.

이 글을 읽으면

  • struct와 class의 유일한 차이를 이해합니다
  • 사용 규칙과 선택 기준을 파악합니다
  • POD 타입과 C 호환성을 확인합니다
  • 실무 시나리오별 선택 전략을 익힙니다

struct vs class 차이

유일한 차이: 기본 접근 제어

항목structclass
기본 접근 제어publicprivate
기본 상속publicprivate
생성자
소멸자
멤버 함수
가상 함수
상속
템플릿

실전 구현

1) 기본 접근 제어

#include <iostream>
// struct: 기본 public
struct Point {
    int x, y;  // public (기본)
};
// class: 기본 private
class Point2 {
    int x, y;  // private (기본)
    
public:
    Point2(int x, int y) : x(x), y(y) {}
    
    int getX() const { return x; }
    int getY() const { return y; }
};
int main() {
    Point p;
    p.x = 10;  // ✅ OK
    p.y = 20;
    
    Point2 p2(10, 20);
    // p2.x = 10;  // ❌ 컴파일 에러: private 멤버
    std::cout << p2.getX() << std::endl;
    
    return 0;
}

2) 상속 기본 접근 제어

#include <iostream>
class Base {
public:
    void foo() {
        std::cout << "Base::foo" << std::endl;
    }
};
// struct: 기본 public 상속
struct DerivedStruct : Base {  // public 상속
};
// class: 기본 private 상속
class DerivedClass : Base {  // private 상속
};
int main() {
    DerivedStruct ds;
    ds.foo();  // ✅ OK (public 상속)
    
    DerivedClass dc;
    // dc.foo();  // ❌ 컴파일 에러 (private 상속)
    
    return 0;
}

3) struct도 class처럼 사용 가능

#include <iostream>
struct MyStruct {
private:  // private 명시 가능
    int x_;
    
public:
    MyStruct(int x) : x_(x) {
        std::cout << "생성자: " << x_ << std::endl;
    }
    
    virtual void foo() {  // 가상 함수
        std::cout << "MyStruct::foo: " << x_ << std::endl;
    }
    
    virtual ~MyStruct() {
        std::cout << "소멸자: " << x_ << std::endl;
    }
};
struct Derived : MyStruct {
    Derived(int x) : MyStruct(x) {}
    
    void foo() override {
        std::cout << "Derived::foo" << std::endl;
    }
};
int main() {
    MyStruct* p = new Derived(42);
    p->foo();  // Derived::foo
    delete p;
    
    return 0;
}

4) 사용 규칙 (Google C++ Style Guide)

struct: 데이터만 담는 수동적 객체

// ✅ struct 사용
struct Point {
    int x, y;
};
struct Color {
    uint8_t r, g, b, a;
};
struct Config {
    std::string host;
    int port;
    bool useSSL;
};

class: 캡슐화와 메서드가 있는 능동적 객체

// ✅ class 사용
class BankAccount {
private:
    double balance_;
    
public:
    BankAccount(double initial) : balance_(initial) {}
    
    void deposit(double amount) {
        if (amount > 0) {
            balance_ += amount;
        }
    }
    
    void withdraw(double amount) {
        if (amount > 0 && balance_ >= amount) {
            balance_ -= amount;
        }
    }
    
    double getBalance() const {
        return balance_;
    }
};

고급 활용

1) POD 타입

POD(Plain Old Data)는 C와 호환되는 단순 타입입니다. POD 조건 (C++11):

  • Trivial 생성자
  • Trivial 소멸자
  • Trivial 복사/이동 연산자
  • Standard layout (private/protected 멤버 없음, 가상 함수 없음)
#include <iostream>
#include <type_traits>
// ✅ POD
struct Point {
    int x, y;
};
static_assert(std::is_pod_v<Point>);  // true
// ❌ 비POD (생성자 있음)
struct Point2 {
    int x, y;
    Point2(int x, int y) : x(x), y(y) {}
};
static_assert(!std::is_pod_v<Point2>);  // false
int main() {
    std::cout << "Point is POD: " << std::is_pod_v<Point> << std::endl;
    std::cout << "Point2 is POD: " << std::is_pod_v<Point2> << std::endl;
    
    return 0;
}

2) C 호환성

// common.h
#ifdef __cplusplus
extern "C" {
#endif
struct Point {
    int x, y;
};
void processPoint(struct Point* p);
#ifdef __cplusplus
}
#endif
// common.cpp
#include "common.h"
#include <iostream>
void processPoint(struct Point* p) {
    std::cout << "Point: (" << p->x << ", " << p->y << ")" << std::endl;
}
// main.cpp
#include "common.h"
int main() {
    Point p = {10, 20};  // C++에서는 struct 키워드 생략 가능
    processPoint(&p);
    
    return 0;
}

3) memcpy 가능 (POD)

#include <cstring>
#include <iostream>
struct Point {
    int x, y;
};
int main() {
    Point p1 = {10, 20};
    Point p2;
    
    std::memcpy(&p2, &p1, sizeof(Point));  // ✅ POD는 memcpy 가능
    
    std::cout << "p2: (" << p2.x << ", " << p2.y << ")" << std::endl;
    
    return 0;
}

성능 비교

struct vs class

#include <chrono>
#include <iostream>
struct PointStruct {
    int x, y;
};
class PointClass {
public:
    int x, y;
};
int main() {
    auto start1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 10000000; ++i) {
        PointStruct p = {i, i};
        int sum = p.x + p.y;
    }
    auto end1 = std::chrono::high_resolution_clock::now();
    auto time1 = std::chrono::duration_cast<std::chrono::milliseconds>(end1 - start1).count();
    
    auto start2 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 10000000; ++i) {
        PointClass p = {i, i};
        int sum = p.x + p.y;
    }
    auto end2 = std::chrono::high_resolution_clock::now();
    auto time2 = std::chrono::duration_cast<std::chrono::milliseconds>(end2 - start2).count();
    
    std::cout << "struct: " << time1 << "ms" << std::endl;
    std::cout << "class: " << time2 << "ms" << std::endl;
    
    return 0;
}

결과 (GCC 13, -O3):

타입시간상대 속도
struct5ms1.0x
class5ms1.0x
결론: 성능 차이 없음

실무 사례

사례 1: 게임 엔진 - 데이터 구조

#include <iostream>
#include <vector>
// struct: 데이터만
struct Transform {
    float x, y, z;
    float rotX, rotY, rotZ;
    float scaleX, scaleY, scaleZ;
};
// class: 캡슐화
class Entity {
private:
    int id_;
    Transform transform_;
    bool active_;
    
public:
    Entity(int id) : id_(id), transform_{0, 0, 0, 0, 0, 0, 1, 1, 1}, active_(true) {}
    
    void setPosition(float x, float y, float z) {
        transform_.x = x;
        transform_.y = y;
        transform_.z = z;
    }
    
    Transform getTransform() const {
        return transform_;
    }
    
    bool isActive() const {
        return active_;
    }
};
int main() {
    Entity entity(1);
    entity.setPosition(10.0f, 20.0f, 30.0f);
    
    Transform t = entity.getTransform();
    std::cout << "Position: (" << t.x << ", " << t.y << ", " << t.z << ")" << std::endl;
    
    return 0;
}

사례 2: 네트워크 - 프로토콜 메시지

#include <cstring>
#include <iostream>
// struct: 네트워크 메시지 (POD)
struct Message {
    uint32_t type;
    uint32_t length;
    char data[256];
};
// class: 메시지 핸들러
class MessageHandler {
public:
    void handleMessage(const Message& msg) {
        std::cout << "Type: " << msg.type << ", Length: " << msg.length << std::endl;
        std::cout << "Data: " << msg.data << std::endl;
    }
};
int main() {
    Message msg;
    msg.type = 1;
    msg.length = 5;
    std::strncpy(msg.data, "Hello", 255);
    msg.data[255] = '\0';
    
    MessageHandler handler;
    handler.handleMessage(msg);
    
    return 0;
}

사례 3: 데이터베이스 - DTO

#include <iostream>
#include <string>
#include <vector>
// struct: DTO (Data Transfer Object)
struct UserDTO {
    int id;
    std::string name;
    std::string email;
    int age;
};
// class: 서비스
class UserService {
public:
    UserDTO getUserById(int id) {
        // 데이터베이스 조회
        return {id, "홍길동", "hong@example.com", 30};
    }
    
    void saveUser(const UserDTO& user) {
        std::cout << "저장: " << user.name << std::endl;
    }
};
int main() {
    UserService service;
    
    UserDTO user = service.getUserById(1);
    std::cout << "이름: " << user.name << std::endl;
    
    user.age = 31;
    service.saveUser(user);
    
    return 0;
}

사례 4: 설정 관리

#include <iostream>
#include <string>
// struct: 설정 데이터
struct ServerConfig {
    std::string host;
    int port;
    int maxConnections;
    bool useSSL;
};
// class: 설정 관리자
class ConfigManager {
private:
    ServerConfig config_;
    
public:
    ConfigManager(const ServerConfig& config) : config_(config) {}
    
    void validate() {
        if (config_.port < 1 || config_.port > 65535) {
            throw std::invalid_argument("잘못된 포트 번호");
        }
        
        if (config_.maxConnections < 1) {
            throw std::invalid_argument("잘못된 최대 연결 수");
        }
    }
    
    ServerConfig getConfig() const {
        return config_;
    }
};
int main() {
    ServerConfig config = {"localhost", 8080, 100, false};
    
    ConfigManager manager(config);
    
    try {
        manager.validate();
        std::cout << "설정 유효" << std::endl;
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
    }
    
    return 0;
}

트러블슈팅

문제 1: struct에 private 멤버

증상: 의도와 다른 접근 제어

// ❌ struct에 private (혼란스러움)
struct Point {
private:  // struct인데 private?
    int x, y;
    
public:
    Point(int x, int y) : x(x), y(y) {}
    int getX() const { return x; }
};
// ✅ class 사용
class Point {
private:
    int x, y;
    
public:
    Point(int x, int y) : x(x), y(y) {}
    int getX() const { return x; }
};

문제 2: class에 모두 public

증상: 의도와 다른 접근 제어

// ❌ class에 모두 public (혼란스러움)
class Point {
public:
    int x, y;  // class인데 public?
};
// ✅ struct 사용
struct Point {
    int x, y;
};

문제 3: POD 조건 위반

증상: C 호환 불가

// ❌ 비POD (생성자 있음)
struct Point {
    int x, y;
    Point(int x, int y) : x(x), y(y) {}
};
static_assert(!std::is_pod_v<Point>);  // false
// ✅ POD
struct Point2 {
    int x, y;
};
static_assert(std::is_pod_v<Point2>);  // true
// C 호환
extern "C" {
    void processPoint(Point2* p);
}

문제 4: 상속 접근 제어 혼란

증상: 의도와 다른 상속

class Base {
public:
    void foo() {}
};
// ❌ struct인데 private 상속 (명시 필요)
struct Derived : private Base {  // private 명시
};
Derived d;
// d.foo();  // ❌ 컴파일 에러 (private 상속)
// ✅ struct는 기본 public 상속
struct Derived2 : Base {  // public 상속 (기본)
};
Derived2 d2;
d2.foo();  // ✅ OK

마무리

struct와 class의 차이기본 접근 제어뿐입니다.

핵심 요약

  1. struct vs class
    • struct: 기본 public
    • class: 기본 private
    • 기능은 완전히 동일
  2. 사용 규칙
    • 데이터만: struct
    • 메서드 + 캡슐화: class
    • POD 필요: struct
  3. POD 타입
    • C 호환
    • memcpy 가능
    • 바이너리 직렬화 가능
  4. 성능
    • struct와 class는 성능 차이 없음
    • 캡슐화가 성능에 영향 없음

선택 가이드

상황권장이유
데이터만struct의도 명확
캡슐화 필요class불변식 보호
C 호환struct (POD)바이너리 호환
메서드 많음class관례
간단한 값 타입struct간결

코드 예제 치트시트

// struct: 데이터만
struct Point {
    int x, y;
};
// class: 캡슐화
class BankAccount {
private:
    double balance_;
    
public:
    BankAccount(double initial) : balance_(initial) {}
    void deposit(double amount) { balance_ += amount; }
    double getBalance() const { return balance_; }
};
// POD 확인
static_assert(std::is_pod_v<Point>);
// C 호환
extern "C" {
    void processPoint(Point* p);
}

다음 단계

참고 자료

  • “Effective C++” - Scott Meyers
  • “C++ Primer” - Stanley Lippman
  • Google C++ Style Guide: https://google.github.io/styleguide/cppguide.html 한 줄 정리: struct는 데이터 컨테이너, class는 캡슐화된 객체로 사용하며, 기능은 완전히 동일하지만 의도를 명확히 표현하는 것이 중요하다.

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. C++ struct vs class 차이는 기본 public/private뿐, 기능은 동일합니다. 데이터 묶음 vs 캡슐화 관례, POD·C 호환까지 문법은 같고 의도 표현과 선택 기준을 비교합니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, struct, class, 접근제어, POD, 자료구조, 객체지향 등으로 검색하시면 이 글이 도움이 됩니다.