C++ struct vs class | Access Control·POD
이 글의 핵심
C++ struct vs class difference is only default public/private, functionality is identical. Data grouping vs encapsulation convention, POD·C compatibility. Same syntax, compares intent expression and selection criteria.
Introduction
In C++, struct and class differ only in default access control, functionality is completely identical.
To use an analogy, the syntactic difference is like whether to put a ‘public’ sticker on the meeting room door or ‘private’ is default, and what you can do inside the room (methods, inheritance, etc.) is the same. By convention, struct is often used for data grouping, class for objects maintaining invariants.
After Reading This
- Understand the only difference between struct and class
- Grasp usage rules and selection criteria
- Check POD type and C compatibility
- Learn selection strategy by practical scenario
Table of Contents
- struct vs class Difference
- Practical Implementation
- Advanced Usage
- Performance Comparison
- Practical Cases
- Troubleshooting
- Conclusion
struct vs class Difference
Only Difference: Default Access Control
| Item | struct | class |
|---|---|---|
| Default access control | public | private |
| Default inheritance | public | private |
| Constructor | ✅ | ✅ |
| Destructor | ✅ | ✅ |
| Member functions | ✅ | ✅ |
| Virtual functions | ✅ | ✅ |
| Inheritance | ✅ | ✅ |
| Templates | ✅ | ✅ |
Practical Implementation
1) Basic Access Control
#include <iostream>
// struct: default public
struct Point {
int x, y; // public (default)
};
// class: default private
class Point2 {
int x, y; // private (default)
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; // ❌ Compile error: private member
std::cout << p2.getX() << std::endl;
return 0;
}
2) Inheritance Default Access Control
#include <iostream>
class Base {
public:
void foo() {
std::cout << "Base::foo" << std::endl;
}
};
// struct: default public inheritance
struct DerivedStruct : Base { // public inheritance
};
// class: default private inheritance
class DerivedClass : Base { // private inheritance
};
int main() {
DerivedStruct ds;
ds.foo(); // ✅ OK (public inheritance)
DerivedClass dc;
// dc.foo(); // ❌ Compile error (private inheritance)
return 0;
}
3) struct Can Be Used Like class
#include <iostream>
struct MyStruct {
private: // Can specify private
int x_;
public:
MyStruct(int x) : x_(x) {
std::cout << "Constructor: " << x_ << std::endl;
}
virtual void foo() { // Virtual function
std::cout << "MyStruct::foo: " << x_ << std::endl;
}
virtual ~MyStruct() {
std::cout << "Destructor: " << 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) Usage Rules (Google C++ Style Guide)
struct: Passive Object Containing Only Data
// ✅ Use struct
struct Point {
int x, y;
};
struct Color {
uint8_t r, g, b, a;
};
struct Config {
std::string host;
int port;
bool useSSL;
};
class: Active Object with Encapsulation and Methods
// ✅ Use 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_;
}
};
Advanced Usage
1) POD Type
POD (Plain Old Data) is a simple type compatible with C. POD Conditions (C++11):
- Trivial constructor
- Trivial destructor
- Trivial copy/move operators
- Standard layout (no private/protected members, no virtual functions)
#include <iostream>
#include <type_traits>
// ✅ POD
struct Point {
int x, y;
};
static_assert(std::is_pod_v<Point>); // true
// ❌ Non-POD (has constructor)
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;
}
Performance Comparison
There Is No Runtime Cost Difference
Since struct and class differ only in default access control, the compiler generates identical machine code for equivalent definitions. Access specifiers (public/private/protected) are a compile-time-only concept — they leave no trace in the binary.
#include <type_traits>
struct PointStruct { int x, y; };
class PointClass {
public:
int x, y;
};
// Both have identical layout, alignment, and size.
static_assert(sizeof(PointStruct) == sizeof(PointClass));
static_assert(std::is_standard_layout_v<PointStruct> == std::is_standard_layout_v<PointClass>);
Where Performance Actually Comes From: POD/Trivial Layout
The real performance lever is not struct vs class, but whether the type stays trivial and standard-layout. A trivial type can be memcpy’d, zero-initialized with {}, and passed in registers by the ABI — a non-trivial type (with a user-defined constructor, virtual function, or non-trivial member) loses these optimizations regardless of whether it is declared with struct or class.
| Type | Copyable via memcpy | Aggregate init {} | Passed in registers (ABI) |
|---|---|---|---|
| Trivial struct/class (POD-like) | ✅ | ✅ | ✅ (small types) |
| Has user constructor | ❌ | ❌ | Depends on ABI |
| Has virtual function | ❌ | ❌ | ❌ (vtable pointer) |
#include <cstring>
struct Vec3 { float x, y, z; }; // trivial: safe to memcpy
void copyFast(Vec3* dst, const Vec3* src, size_t n) {
std::memcpy(dst, src, n * sizeof(Vec3)); // ✅ fast bulk copy
}
Takeaway: choosing struct over class never buys you speed by itself. Keeping the type trivial does.
Practical Cases
1) DTO / Wire-Format Struct
Data-only types that cross a boundary (network, file, IPC) are the canonical struct use case — no invariants to protect, just a bag of fields.
struct PacketHeader {
uint16_t version;
uint16_t length;
uint32_t checksum;
}; // trivial layout: safe to serialize with memcpy
2) RAII Wrapper as class
Anything that owns a resource and must maintain an invariant (e.g. “the handle is always valid or null”) belongs in a class, so the invariant can only be touched through a controlled interface.
class FileHandle {
FILE* fp_ = nullptr;
public:
explicit FileHandle(const char* path) : fp_(std::fopen(path, "r")) {}
~FileHandle() { if (fp_) std::fclose(fp_); }
FileHandle(const FileHandle&) = delete; // no accidental double-close
FileHandle& operator=(const FileHandle&) = delete;
FILE* get() const { return fp_; }
};
3) Math/Vector Types (Graphics, Physics Libraries)
Libraries like GLM follow the convention of struct for math primitives because every field is meant to be read and written directly, and there is no invariant beyond “these are three floats.”
struct Vec3 {
float x, y, z;
Vec3 operator+(const Vec3& o) const { return {x + o.x, y + o.y, z + o.z}; }
};
4) Config/Options Struct Passed by Value
struct also reads well as a named-parameter substitute — grouping optional settings without forcing callers through getters/setters.
struct RetryOptions {
int maxAttempts = 3;
int backoffMs = 100;
bool jitter = true;
};
void connect(const RetryOptions& opts = {});
Troubleshooting
1) std::is_pod Deprecated Since C++20
is_pod/is_pod_v were deprecated in C++20 because “POD” conflated two separate ideas. Use the split traits instead.
#include <type_traits>
// ❌ Deprecated (C++20+): may warn or be removed by your compiler
// static_assert(std::is_pod_v<Point>);
// ✅ Use instead
static_assert(std::is_trivial_v<Point>);
static_assert(std::is_standard_layout_v<Point>);
2) Mixing struct/class on Forward Declarations
Declaring a type as struct in one place and class in another compiles under most compilers but MSVC emits warning C4099 (“type name first seen using ‘class’ now seen using ‘struct’”). Keep the keyword consistent across declarations and definitions.
class Widget; // declared as class
// ...
struct Widget { ... }; // ⚠️ MSVC C4099: mismatched keyword
3) Forgetting struct’s Default-Public Members Break Encapsulation
A common bug is starting a type as a simple data struct, then adding behavior and invariants later without switching to class (or adding an explicit private:) — every member stays public and the invariant can be violated from outside.
struct Account {
double balance; // ⚠️ still public — anyone can set a negative balance
void withdraw(double amt) { if (amt <= balance) balance -= amt; }
};
account.balance = -1000; // invariant bypassed entirely
Fix: once a type needs to protect an invariant, either add private: explicitly or switch to class to make the intent unmistakable.
4) Private Inheritance Surprise with class
Because class defaults to private inheritance, forgetting public on the base class silently changes the inheritance semantics and breaks is-a relationships (e.g., polymorphic calls through a base pointer stop compiling).
class Derived : Base { ... }; // ⚠️ private inheritance by default
class Derived : public Base { ... }; // ✅ what you almost always want
Summary
Key Points
- struct vs class: Only default access control differs
- struct: For data grouping (default public)
- class: For encapsulation (default private)
- POD: Simple type compatible with C
- Convention: struct for data, class for objects
Decision Flowchart
Need encapsulation?
├─ Yes → class
└─ No (only data)
└─ Need C compatibility?
├─ Yes → struct (POD)
└─ No → struct (convention)
Best Practices
- ✅ Use struct for data-only types
- ✅ Use class for objects with behavior
- ✅ Check POD for C compatibility
- ❌ Don’t mix conventions
- ❌ Don’t use struct for complex objects
Related Articles
Master struct vs class selection! 🚀
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. C++ struct vs class difference is only default public/private, functionality is identical.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- C++ Aggregate Initialization 완벽 가이드 | 집합 초기화
- C++ Aggregates & Aggregate Initialization
- Complete Guide to C++ Aggregate Initialization
Keywords Covered in This Article (Related Search Terms)
This article covers C++, struct, class, access-control, POD, data-structure, OOP.