C++ 클래스

C++에서 클래스는 `class`, `struct` 또는 `union` 키워드 중 하나로 선언된 사용자 정의 자료형 또는 자료 구조이며 (처음 두 개는 비-유니온 클래스라고 총칭함) 데이터와 함수(또한 멤버 변수와 멤버 함수라고도 함)를 멤버로 가지며, 이들의 접근은 private, protected 또는 public 세 가지 접근 지정자에 의해 관리된다. 기본적으로 `class` 키워드로 선언된 C++ 클래스의 멤버에 대한 접근은 private이다. private 멤버는 클래스 외부에서 접근할 수 없으며, 클래스의 멤버 함수를 통해서만 접근할 수 있다. public 멤버는 클래스에 대한 인터페이스를 형성하며 클래스 외부에서 접근할 수 있다. 클래스 자료형의 인스턴스는 객체로 알려져 있으며, 프로그래머가 정의한 멤버 변수, 상수, 멤버 함수, 오버로드된 연산자를 포함할 수 있다.
# C++에서 `struct`와 `class`의 차이
C++에서 `class` 키워드로 정의된 클래스는 기본적으로 private 멤버와 기본 클래스를 가진다. 구조체는 `struct` 키워드로 정의된 클래스이다.ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §9 Classes [class] para. 4 그 멤버와 기본 클래스는 기본적으로 public이다. 실제로는 구조체는 일반적으로 함수가 없는 데이터에 사용된다. 클래스/구조체로부터 구조체를 파생시킬 때, 기본 클래스/구조체에 대한 기본 접근 지정자는 public이다. 그리고 클래스를 파생시킬 때는 기본 접근 지정자는 private이다.
# 집합 클래스
집합 클래스는 사용자 선언 생성자, private 또는 protected 비정적 데이터 멤버, 기본 클래스, 가상 함수가 없는 클래스이다.ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §8.5.1 Aggregates [dcl.init.aggr] para. 1 이러한 클래스는 중괄호로 묶인 쉼표로 구분된 초기화 구문 목록으로 초기화할 수 있다.ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §8.5.1 Aggregates [dcl.init.aggr] para. 2 다음 코드는 C와 C++ 모두에서 동일한 의미를 가진다. struct C { int a; double b; }; struct D { int a; double b; C c; }; // initialize an object of type C with an initializer-list C c = {1, 2.0}; // D has a sub-aggregate of type C. In such cases initializer-clauses can be nested D d = {10, 20.0, {1, 2.0;
## POD-구조체
POD-구조체 (Plain Old Data Structure)는 비-POD-구조체, 비-POD-유니온(또는 그러한 유형의 배열) 또는 참조 유형의 비정적 데이터 멤버가 없고, 사용자 정의 대입 연산자 및 사용자 정의 소멸자가 없는 비-유니온 집합 클래스이다. POD-구조체는 C++에서 C의 `struct`에 해당한다고 할 수 있다. 대부분의 경우, POD-구조체는 C에서 선언된 해당 구조체와 동일한 메모리 레이아웃을 가진다.url-status=dead 이러한 이유로 POD-구조체는 때때로 구어적으로 "C-스타일 구조체"라고 불린다.url=https://archive.org/details/industrialstreng0000henr
### C의 구조체와 C++의 POD-구조체 간의 공유 속성
*데이터 멤버는 접근 지정자로 구분된 경우를 제외하고는 객체 내에서 나중 멤버가 더 높은 주소를 가지도록 할당된다.ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §9.2 Class members [class.mem] para. 12 *두 POD-구조체 유형은 동일한 수의 비정적 데이터 멤버를 가지고, 해당 비정적 데이터 멤버(순서대로)가 레이아웃 호환 가능한 유형을 가지면 레이아웃 호환 가능하다.ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §9.2 Class members [class.mem] para. 14 *POD-구조체는 이름 없는 패딩을 포함할 수 있다.ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §9.2 Class members [class.mem] para. 17 *reinterpret_cast를 사용하여 적절하게 변환된 POD-구조체 객체에 대한 포인터는 해당 초기 멤버를 가리키고 그 반대도 마찬가지이며, 이는 POD-구조체의 시작 부분에 패딩이 없음을 의미한다. *POD-구조체는 offsetof 매크로와 함께 사용될 수 있다.ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §18.1 Types [lib.support.types] para. 5
# 선언 및 사용법
C++ 클래스는 자체 멤버를 가진다. 이 멤버에는 변수(다른 구조체 및 클래스 포함), 멤버 함수로 알려진 함수(특정 식별자 또는 오버로드된 연산자), 생성자 및 소멸자가 포함된다. 멤버는 `public:` 및 `private:` 접근 지정자를 사용하여 공개적으로 또는 비공개적으로 접근 가능하도록 선언된다. 지정자 다음에 나오는 모든 멤버는 다른 지정자가 나올 때까지 해당 접근 권한을 가진다. 클래스 간에는 `protected:` 지정자를 사용할 수 있는 상속도 있다.
## this 키워드
this (컴퓨터 과학) 클래스가 자신을 참조할 수 있는 기능을 용이하게 하기 위해 C++은 모든 멤버 함수에 대해 `this` 키워드를 구현한다. `this` 키워드는 현재 객체에 대한 포인터 역할을 한다.work=C++ Reference 그 유형은 현재 객체에 대한 포인터이다. `this` 키워드는 클래스 자체가 반환 값인 멤버 함수에 특히 중요하다. Complex& operator+=(const Complex& c) noexcept { this->realPart += c.realPart; this->imagPart += c.imagPart; return *this; } 위에서 언급했듯이, `this`는 포인터이므로 반환할 참조로 변환하려면 별표 (*)를 사용하는 것이 필요하다.
## 전역 및 지역 클래스
모든 함수 외부에서 정의된 클래스는 전역 클래스이다. 객체가 프로그램의 어디에서든 생성될 수 있기 때문이다. 함수 본문 내에서 정의되면 지역 클래스이다. 해당 클래스의 객체는 함수 범위에 지역적이기 때문이다.
## 기본 선언 및 멤버 변수
비-유니온 클래스는 `class` 또는 `struct` 키워드로 선언된다. 멤버 선언은 이 선언 내에 위치한다. {| | | using std::string; struct Person { string name; int age; }; | using std::string; class Person { public: string name; int age; }; |} 위 정의들은 기능적으로 동일하다. 두 코드 모두 `Person` 유형의 객체가 두 개의 public 데이터 멤버, `name`과 `age`를 가지도록 정의한다. 닫는 중괄호 뒤의 쌍반점은 필수이다. 이 선언들 중 하나(둘 다 아님) 후에, `Person`은 다음과 같이 사용하여 `Person` 데이터 유형의 새로 정의된 변수를 생성할 수 있다. import std; using std::string; struct Person { string name; int age; }; int main() { Person a; Person b; a.name = "Calvin"; b.name = "Hobbes"; a.age = 30; b.age = 20; std::println("{}: {}", a.name, a.age); std::println("{}: {}", b.name, b.age); } 위 코드를 실행하면 다음이 출력된다.

Calvin: 30
Hobbes: 20

## 멤버 함수
C++ 클래스의 중요한 특징은 **멤버 함수**이다. 각 자료형은 자체 내장 함수(멤버 함수라고 함)를 가질 수 있으며, 이 함수는 자료형의 모든(public 및 private) 멤버에 접근할 수 있다. 이러한 비정적 멤버 함수의 본문에서는 `this` 키워드를 사용하여 함수가 호출된 객체를 참조할 수 있다. 이는 일반적으로 객체의 주소를 함수의 암시적 첫 번째 인수로 전달함으로써 구현된다.accessdate = 2009-01-26 위 `Person` 유형을 다시 예로 들어보자. import std; using std::string; class Person { private: string name; int age = 5; public: // "name" and "age" are the member variables. The "this" keyword is an // expression whose value is the address of the object for which the member // was invoked. Its type is "const Person*", because the function is declared // const. void printData() const { std::println("{}: {}", name, age); } }; 위 예제에서 `printData` 메서드는 클래스 본문에 선언되어 있으며 클래스 이름 뒤에 `::`를 붙여 한정함으로써 정의된다. `name`과 `age`는 모두 private(클래스의 기본값)이며 `printData`는 클래스 외부에서 사용하려면 public으로 선언되어야 한다. 멤버 함수 `printData`를 사용하면 출력은 다음으로 간소화될 수 있다. a.printData(); b.printData(); 여기서 위에서 `a`와 `b`는 송신자라고 불리며, `printData()` 함수가 실행될 때 각자는 자신의 멤버 변수를 참조한다. 모듈이 도입되기 전(즉, 헤더를 사용하던 시점)에는 일반적으로 클래스 또는 구조체 선언(인터페이스라고 함)과 정의(구현이라고 함)를 별도의 단위로 분리했다. 사용자가 필요로 하는 인터페이스는 헤더 파일에 보관하고 구현은 소스 또는 컴파일된 형태로 별도로 보관했다.
## 상속
C++ 표준은 비POD 클래스의 메모리 레이아웃을 지정하지 않는다. 예를 들어, 많은 인기 있는 C++ 컴파일러는 부모 클래스 필드와 자식 클래스 필드를 연결하여 단일 상속을 구현하지만, 이는 표준에서 요구하는 바가 아니다. 이러한 레이아웃 선택은 부모 클래스 유형에 대한 포인터를 통해 파생 클래스를 참조하는 것을 간단한 작업으로 만든다. 예를 들어 다음을 고려해 보자. struct P { int x; }; struct C : public P { int y; }; `P* p`가 가리키는 `P`의 인스턴스는 메모리에서 다음과 같이 보일 수 있다. ┏━━━━┓ ┃P::x┃ ┗━━━━┛ ↑ p `P* p`가 가리키는 `C`의 인스턴스는 다음과 같이 보일 수 있다. ┏━━━━┳━━━━┓ ┃P::x┃C::y┃ ┗━━━━┻━━━━┛ ↑ p 따라서 `P` 객체의 필드를 조작하는 모든 코드는 `C`의 필드 정의에 대해 아무것도 고려할 필요 없이 `C` 객체 내부의 `P` 필드를 조작할 수 있다. 어떠한 경우에도 올바르게 작성된 C++ 프로그램은 상속된 필드의 레이아웃에 대해 어떠한 가정도 하지 않아야 한다. static_cast 또는 dynamic_cast 형 변환 연산자를 사용하면 포인터가 한 유형에서 다른 유형으로 올바르게 변환되도록 보장한다. 다중 상속은 그렇게 간단하지 않다. 클래스 `D`가 `P`와 `C`를 상속하는 경우, 두 부모의 필드가 어떤 순서로든 저장되어야 하지만, (최대) 하나의 부모 클래스만 파생 클래스의 시작 부분에 위치할 수 있다. 컴파일러가 포인터를 `D` 유형에서 `P` 또는 `C`로 변환해야 할 때마다 컴파일러는 파생 클래스의 주소에서 기본 클래스 필드의 주소로 자동 변환을 제공한다 (일반적으로 이는 간단한 오프셋 계산이다). 다중 상속에 대한 자세한 내용은 가상 상속을 참조하세요. `final` 키워드는 클래스가 하위 클래스로 만들어지는 방식을 제한한다.ISO International Standard ISO/IEC 14882:2020(E) – Programming Language C++. 클래스의 하위 클래스는 부모 클래스에 의해 `final`로 표시된 메서드를 재정의할 수 없도록 방지된다.language=en final 클래스는 상속될 수 없다. 이를 통해 데비추얼라이제이션(가상화 제거), 즉 메서드 조회를 위해 vtable 사용을 제거하여 최종 클래스에서 메서드 호출을 인라인화할 수 있다.website=16bpp.netlanguage=en-US // class 선언의 final은 클래스가 확장될 수 없음을 선언한다. class Z final : public X, public Y { public: // 메서드 시그니처의 final은 메서드가 더 이상 재정의될 수 없음을 선언한다. void someOperation() override final { // do something here } }; `final`은 C++의 예약어가 아니라, 기존 코드베이스에서 'final' 식별자의 사용과 충돌하지 않도록 문맥 의존적 키워드로 정의된다.language=en-uslanguage=en
## 오버로드된 연산자
C와 C++의 연산자 C++에서 `+ - * /`와 같은 연산자는 프로그래머의 필요에 맞게 오버로드될 수 있다. 이러한 연산자를 **오버로드 가능한 연산자**라고 한다. 관례적으로 오버로드된 연산자는 내장 자료형(`int`, `float` 등)과 거의 동일하게 동작해야 하지만, 이는 필수 사항은 아니다. 프로그래머는 변수가 실제로 정수를 저장하는 `Integer`라는 구조체를 선언할 수 있지만, `Integer * Integer`를 호출하면 정수의 곱이 아닌 합이 반환될 수 있다. struct Integer { Integer() = default; Integer(int j): i{j} {} Integer operator*(const Integer& k) const { return Integer(i + k.i); } int i = 0; }; 위 코드는 반환 값을 "구성"하기 위해 생성자를 사용했다. 더 명확한 표현을 위해 (컴파일러가 이 문장을 위의 동일한 문장으로 최적화할 수 없다면 프로그램 효율성이 저하될 수 있지만), 위 코드는 다음과 같이 다시 작성할 수 있다. Integer operator*(const Integer& k) const { Integer m; m.i = i + k.i; return m; } 프로그래머는 또한 `struct` 선언에 연산자의 프로토타입을 넣고 전역 범위에서 연산자의 함수를 정의할 수 있다. struct Integer { Integer() = default; Integer(int j): i{j} {} Integer operator*(const Integer& k) const; int i = 0; }; Integer Integer::operator*(const Integer& k) const { return Integer(i * k.i); } 위의 `i`는 발신자의 자체 멤버 변수를 나타내고, `k.i`는 인수 변수 `k`의 멤버 변수를 나타낸다. `const` 키워드는 위 코드에 두 번 나타난다. 첫 번째 발생인 인수 `const integer& k`는 인수 변수가 함수에 의해 변경되지 않음을 나타낸다. 선언 끝의 두 번째 발생은 컴파일러에게 함수 실행에 의해 발신자가 변경되지 않을 것을 약속한다. `const integer& k`에서 앰퍼샌드(&)는 "참조에 의한 전달"을 의미한다. 함수가 호출될 때 변수 값 대신 변수에 대한 참조가 함수에 전달된다. 연산자의 결합성과 연산의 우선순위는 변경할 수 없다는 점에 유의해야 한다.
### 이항 오버로드 가능 연산자
이항 연산자(두 개의 인수를 갖는 연산자)는 하나의 단일 인수를 호출하는 "식별자" 연산자(무엇인가)를 사용하여 함수를 선언함으로써 오버로드된다. 연산자의 왼쪽에 있는 변수는 발신자이고 오른쪽에 있는 변수는 인수이다. Integer i = 1; /* 이처럼 구조체 변수를 초기화할 수 있다. 마치 첫 번째 인수만 지정하여 생성자를 호출하는 것과 같다. */ Integer j = 3; /* 변수 이름은 구조체의 멤버 변수 이름과 무관하다. */ Integer k = i * j; std::println("{}", k.i); '3'이 출력될 것이다. 다음은 이항 오버로드 가능 연산자 목록이다. {| class="wikitable" |----- ! 연산자 ! 일반적인 사용법 |----- | + - * / % | 산술 계산 |----- | ^ & ! > | 비트 단위 계산 |----- | == != = | 논리 비교 |----- | && | 논리곱 |----- | || | 논리합 |----- | += -= *= /= %=
^= &= != >= | 복합 대입 |----- | , | (일반적인 사용법 없음) |} 동일한 구조체 유형의 두 변수 사이의 '=' (대입) 연산자는 기본적으로 한 변수에서 다른 변수로 변수 전체 내용을 복사하도록 오버로드된다. 필요한 경우 다른 것으로 덮어쓸 수 있다. 연산자는 하나씩 오버로드해야 한다. 다시 말해, 어떤 오버로딩도 서로 관련되어 있지 않다. 예를 들어, ``의 반대인 것은 아니다.
### 단항 오버로드 가능 연산자
위에서 명시된 일부 연산자는 왼쪽에 발신자, 오른쪽에 인수를 취하는 두 항을 가지지만, 일부 연산자는 발신자라는 하나의 인수만 가지며 "단항"이라고 불린다. 예를 들어 음수 부호(왼쪽에 아무것도 없을 때)와 "논리 NOT" (느낌표, `!`)가 있다. 단항 연산자의 발신자는 연산자의 왼쪽 또는 오른쪽에 있을 수 있다. 다음은 단항 오버로드 가능 연산자 목록이다. {| class="wikitable" |----- ! 연산자 ! 일반적인 사용법 ! 발신자의 위치 |----- | + - || 양수/음수 부호 || 오른쪽 |----- | * & || 역참조 || 오른쪽 |----- | ! ~ || 논리/비트 NOT || 오른쪽 |----- | ++ -- || 전위 증가/감소 || 오른쪽 |----- | ++ -- || 후위 증가/감소 || 왼쪽 |} 발신자가 오른쪽에 있는 단항 연산자의 오버로딩 구문은 다음과 같다. :`return_type operator@ ()` 발신자가 왼쪽에 있을 때 선언은 다음과 같다. :`return_type operator@ (int)` 위의 `@`는 오버로드될 연산자를 나타낸다. `return_type`을 반환 값의 자료형(`int`, `bool`, 구조체 등)으로 대체한다. `int` 매개변수는 본질적으로 아무 의미가 없으며, 발신자가 연산자의 왼쪽에 있음을 보여주는 관례일 뿐이다. 해당하는 경우 선언 끝에 `const` 인수를 추가할 수 있다.
### 괄호 오버로딩
C++ 클래스에서는 대괄호 `[]`와 소괄호 `()`를 오버로드할 수 있다. 대괄호는 정확히 하나의 인수, 초기화 목록(C++11 이후), 또는 임의의 매개변수 팩(C++23 이후)을 포함해야 하며, 소괄호는 특정 개수의 인수 또는 인수를 포함하지 않을 수 있다. 다음 선언은 대괄호를 오버로드한다. :`return_type operator[] (arg1, arg2, ...))` 괄호 안의 내용은 `argument` 부분에 지정된다. 소괄호는 비슷한 방식으로 오버로드된다. :`return_type operator() (arg1, arg2, ...)` 연산자 호출에서 괄호의 내용은 두 번째 괄호 안에 지정된다. 위에서 명시된 연산자 외에 화살표 연산자(`->`), 별표 화살표(`->*`), `new` 키워드, `delete` 키워드도 오버로드될 수 있다. 이 메모리 또는 포인터 관련 연산자들은 오버로드 후 메모리 할당 함수를 처리해야 한다. 대입(`=`) 연산자처럼, 특별한 선언이 없으면 기본적으로 오버로드된다.
## 생성자
때때로 프로그래머는 변수가 선언될 때 기본값 또는 특정 값을 가지도록 원할 수 있다. 이는 생성자를 선언하여 수행할 수 있다. using std::string; Person::Person(string name, int age) { this->name = name; this->age = age; } 아래 예와 같이 콜론을 사용하여 초기화 목록에서 멤버 변수를 초기화할 수 있다. 이는 위와는 달리 대입 연산자를 사용하는 것이 아니라 (생성자를 사용하여) 초기화하는 방식이다. 클래스 유형의 경우 직접 구성되기만 하면 되므로 더 효율적이다. 반면 대입의 경우 기본 생성자를 사용하여 먼저 초기화한 다음 다른 값을 대입해야 한다. 또한 일부 유형(참조 및 const 유형 등)은 대입할 수 없으므로 초기화 목록에서 초기화해야 한다. using std::string; Person(string name, int age): name{name}, age{age} {} 중괄호는 비어 있어도 생략할 수 없다는 점에 유의해야 한다. 기본값 초기화를 돕기 위해 마지막 인수에 기본값을 지정할 수 있다. using std::string; Person(string name = "", int age = 0): name{name}, age{age} {} 위 예시의 생성자에 인수가 주어지지 않으면, 인수가 없는 다음 생성자(기본 생성자)를 호출하는 것과 동일하다. Person(): name{""}, age{0} {} 생성자 선언은 자료형과 이름이 같은 함수처럼 보인다. 실제로 생성자 호출은 함수 호출 형태를 취할 수 있다. 이 경우 초기화된 `Person` 유형 변수는 반환 값으로 생각할 수 있다. int main() { Person r = Person("Wales", 40); r.print(); } 위 예제와 동일한 작업을 수행하는 다른 구문은 다음과 같다. int main() { Person r("Wales", 40); r.printData(); } 변수와 관련이 있거나 없을 수 있는 특정 프로그램 동작을 생성자의 일부로 추가할 수 있다. Person() { std::println("Hello!"); } 위 생성자를 사용하면 기본 `Person` 생성자가 호출될 때 "Hello!"가 출력된다.
## 기본 생성자
클래스에 생성자가 정의되지 않았을 때 기본 생성자가 호출된다. struct A { int b; }; // 괄호를 사용하여 객체 생성. A* a = new A(); // 기본 생성자를 호출하며, b는 '0'으로 초기화된다. // 괄호 없이 객체 생성. A* a = new A; // 메모리를 할당한 다음 기본 생성자를 호출하며, b는 '0' 값을 갖는다. // new 없이 객체 생성. A a; // 스택에 a를 위한 공간을 예약하며, b는 알 수 없는 쓰레기 값을 갖는다. 그러나 클래스에 사용자 정의 생성자가 정의된 경우, 위의 두 선언 모두 이 사용자 정의 생성자를 호출하여 정의된 코드가 실행되지만, 변수 b에 기본값은 할당되지 않는다.
## 소멸자
소멸자는 생성자의 역이다. 클래스 인스턴스가 소멸될 때 호출된다. 예를 들어, 블록(중괄호 "{}" 집합) 내에서 생성된 클래스 객체가 닫는 중괄호 다음에 삭제되면 소멸자가 자동으로 호출된다. 변수를 저장하는 메모리 위치가 비워질 때 호출된다. 소멸자는 해당 클래스 인스턴스가 소멸될 때 힙 할당 메모리 및 열린 파일과 같은 리소스를 해제하는 데 사용될 수 있다. 소멸자를 선언하는 구문은 생성자와 유사하다. 반환 값이 없으며 함수 이름은 클래스 이름과 동일하고 앞에 물결표(~)가 붙는다. ~Person() { std::println("I'm deleting {} with age {}", name, age); }
# 생성자와 소멸자의 유사점
*둘 다 자신이 선언된 클래스와 이름이 같다. *사용자가 선언하지 않으면 둘 다 기본적으로 클래스에서 사용 가능하지만, 이제 객체가 선언되거나 삭제될 때 클래스 객체로부터 메모리를 할당하거나 해제할 수만 있다. *파생 클래스의 경우: 기본 클래스 생성자 실행 중에는 파생 클래스 생성자가 아직 호출되지 않는다. 기본 클래스 소멸자 실행 중에는 파생 클래스 소멸자가 이미 호출되었다. 두 경우 모두 파생 클래스 멤버 변수는 유효하지 않은 상태이다.
# 클래스 템플릿
템플릿 (프로그래밍) C++에서는 클래스 템플릿으로부터 클래스 선언을 생성할 수 있다. 이러한 클래스 템플릿은 클래스들의 패밀리를 나타낸다. 실제 클래스 선언은 하나 이상의 템플릿 인수를 사용하여 템플릿을 인스턴스화함으로써 얻어진다. 특정 인수 집합으로 인스턴스화된 템플릿은 템플릿 특수화라고 불린다.
# 속성
C++ 구문은 클래스의 모든 측면이 기본 자료형과 같아 보이도록 노력한다. 따라서 오버로드된 연산자는 클래스를 정수 및 부동 소수점 숫자처럼 조작할 수 있도록 하고, 클래스 배열은 대괄호 구문(`some_structure variable_name[size]`)으로 선언할 수 있으며, 클래스 포인터는 내장 자료형 포인터와 같은 방식으로 역참조될 수 있다.
## 메모리 소비
구조체의 메모리 소비는 최소한 구성 변수들의 메모리 크기 합계이다. 아래 `TwoNums` 구조체를 예로 들어보자. struct TwoNums { int a; int b; }; 이 구조체는 두 개의 정수로 구성된다. 현재 많은 C++ 컴파일러에서 정수는 기본값으로 32비트 정수이므로 각 멤버 변수는 4바이트의 메모리를 소비한다. 따라서 전체 구조체는 최소한 (또는 정확히) 8바이트의 메모리를 소비하며, 다음과 같다. +----+----+ | a | b | +----+----+ 그러나 컴파일러는 주어진 컴퓨터 아키텍처에 대한 적절한 데이터 정렬을 보장하기 위해 변수 사이에 또는 구조체 끝에 패딩을 추가할 수 있으며, 종종 변수를 32비트 정렬로 패딩한다. 예를 들어, 구조체 struct BytesAndSuch { char c; char C; char D; short int s; int i; double d; }; 는 메모리에서 다음과 같이 보일 수 있다. +-+-+-+-+--+--+----+--------+ |c|C|D|X|s |XX| i | d | +-+-+-+-+--+--+----+--------+ 여기서 X는 4바이트 정렬을 기반으로 하는 패딩 바이트를 나타낸다. 구조체는 멤버 변수를 선언하고 초기화하기 위해 포인터와 배열을 사용할 수 있으므로 구조체의 메모리 소비는 반드시 상수가 아니다. 비상수 메모리 크기의 또 다른 예는 템플릿 구조체이다.
### 비트 필드
비트 필드는 정수형보다 적은 저장 공간을 차지할 수 있는 클래스 멤버를 정의하는 데 사용된다. 이 필드는 정수형(int, char, short, long 등)과 열거형(예: std::byte)에만 적용되며 float 또는 double은 제외된다. struct A { unsigned a:2; // 가능한 값 0..3, int의 처음 2비트 차지 unsigned b:3; // 가능한 값 0..7, int의 다음 3비트 차지 unsigned :0; // 다음 정수형의 끝으로 이동 unsigned c:2; unsigned :4; // c와 d 사이에 4비트 패딩 unsigned d:1; unsigned e:3; }; * 메모리 구조

4 byte int 4 byte int
[1][2][3][4][5][6][7][8]
[1] [2] [3] [4]
[a][a][b][b][b][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ]

[5] [6] [7] [8]
[c][c][ ][ ][ ][ ][d][e] [e][e][ ][ ][ ][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ]

공용체도 비트 필드 멤버를 가질 수 있다. union A { unsigned a:2; unsigned b:3; unsigned :0; // 아무것도 하지 않음 unsigned c:2; unsigned :4; // 이 경우 아무것도 하지 않음; 비트 필드의 너비가 충분히 컸다면 공용체의 크기를 맞추기 위해 변경되었을 것 unsigned d:1; unsigned e:3; };
## 참조로 전달
평가 전략 (컴퓨터 프로그래밍) 많은 프로그래머는 구조체를 포함하는 함수의 인수를 선언할 때 앰퍼샌드(&)를 사용하는 것을 선호한다. 이는 역참조 앰퍼샌드를 사용하면 변수의 메모리 위치, 즉 단어 하나(일반적으로 32비트 머신에서는 4바이트, 64비트 머신에서는 8바이트)만 함수에 전달하면 되기 때문이다. 그렇지 않으면 값으로 전달을 사용하면 함수가 호출될 때마다 인수를 복사해야 하는데, 이는 큰 구조체의 경우 비용이 많이 든다. 참조에 의한 전달은 원래 구조체가 함수에 의해 수정될 수 있도록 노출시키므로, 의도하지 않은 경우에는 `const` 키워드를 사용하여 함수가 매개변수를 수정하지 않음을 보장해야 한다(const-정확성 참조).