객체
C++ 프로그램은 객체를 생성, 소멸, 참조, 접근, 조작합니다.
C++에서 객체는 다음과 같은 특성을 가집니다:
- 크기 (
sizeof로 결정 가능); - 정렬 요구사항 (
alignof로 결정 가능); - 저장 기간 (자동, 정적, 동적, 스레드 지역);
- 수명 (저장 기간 또는 임시 객체에 의해 제한됨);
- 타입;
- 값 (예를 들어 기본 초기화된 비클래스 타입의 경우 불확정적일 수 있음);
- 선택적으로, 이름.
다음 엔터티는 객체가 아닙니다: 값, 참조, 함수, 열거자, 타입, 비정적 클래스 멤버, 템플릿, 클래스 또는 함수 템플릿 특수화, 네임스페이스, 매개변수 팩, 그리고 this.
변수는 비정적 데이터 멤버가 아닌 객체 또는 참조로, 선언에 의해 도입됩니다.
객체 생성
객체는 정의, new 표현식, throw 표현식, 공용체의 활성 멤버 변경, 그리고 임시 객체를 필요로 하는 표현식 평가를 통해 명시적으로 생성될 수 있습니다. 생성된 객체는 명시적 객체 생성에서 고유하게 정의됩니다.
암시적 수명 타입의 객체는 다음에 의해 암시적으로 생성될 수도 있습니다:
- 상수 평가 중을 제외하고, 타입
unsigned char또는 std::byte(C++17부터)의 배열 수명을 시작하는 연산. 이 경우 그러한 객체들이 배열에 생성됩니다. - 다음 할당 함수 호출. 이 경우 그러한 객체들이 할당된 저장소에 생성됩니다:
- operator new (상수 평가 중 제외)
- operator new[] (상수 평가 중 제외)
- std::malloc
- std::calloc
- std::realloc
|
(C++17부터) |
- 다음 객체 표현 복사 함수 호출. 이 경우 그러한 객체들이 대상 저장소 영역 또는 결과에 생성됩니다:
- std::memcpy
- std::memmove
| (C++20부터) |
|
(C++23부터) |
동일한 저장소 영역에 0개 이상의 객체가 생성될 수 있으며, 단 그렇게 해도 프로그램에 정의된 동작이 주어져야 합니다. 그러한 생성이 불가능한 경우(예: 충돌하는 연산으로 인해) 프로그램의 동작은 정의되지 않습니다. 여러 개의 암시적 생성 객체 집합이 프로그램에 정의된 동작을 제공한다면, 어떤 객체 집합이 생성되는지는 명시되지 않습니다. 즉, 암시적 생성 객체는 반드시 고유하게 정의될 필요가 없습니다.
지정된 저장소 영역 내에서 객체를 암시적으로 생성한 후, 일부 연산은 적절하게 생성된 객체에 대한 포인터를 생성합니다. 적절하게 생성된 객체는 저장소 영역과 동일한 주소를 갖습니다. 마찬가지로, 그러한 포인터 값이 프로그램에 정의된 동작을 제공할 수 없는 경우에만 동작이 정의되지 않으며, 프로그램에 정의된 동작을 제공하는 여러 값이 있는 경우 어떤 포인터 값이 생성되는지는 명시되지 않습니다.
#include <cstdlib>
struct X { int a, b; };
X* MakeX()
{
// One of possible defined behaviors:
// the call to std::malloc implicitly creates an object of type X
// and its subobjects a and b, and returns a pointer to that X object
X* p = static_cast<X*>(std::malloc(sizeof(X)));
p->a = 1;
p->b = 2;
return p;
}
std::allocator::allocate 호출 또는 공용체 타입의 암시적으로 정의된 복사/이동 특수 멤버 함수도 객체를 생성할 수 있습니다.
객체 표현과 값 표현
일부 타입과 객체는 객체 표현과 값 표현을 가지며, 아래 표에 정의되어 있습니다:
| 엔터티 | 객체 표현 | 값 표현 |
|---|---|---|
완전한 객체 타입 T
|
타입 N unsigned char의 비비트 필드 완전한 객체가 차지하는 T 객체들의 시퀀스. 여기서 N는 sizeof(T)
|
타입 T의 값 표현에 참여하는 객체 표현의 비트 집합T
|
비비트 필드 완전한 객체 obj (타입 T
|
)obj의 객체 표현에 해당하는 T
|
의 바이트obj의 값 표현에 해당하는 T
|
의 비트bf
|
비트 필드 객체 N가 차지하는 bf 비트들의 시퀀스. 여기서 N는 비트 필드의 너비
|
타입 bf의 값 표현에 참여하는 객체 표현의 비트 집합bf
|
타입이나 객체의 객체 표현에서 값 표현의 일부가 아닌 비트는 패딩 비트입니다.
TriviallyCopyable 타입의 경우, 값 표현은 객체 표현의 일부입니다. 즉, 저장소에서 객체가 차지하는 바이트를 복사하는 것으로 동일한 값을 가진 다른 객체를 생성하기에 충분합니다(단, 객체가 잠재적으로 겹치는 하위 객체이거나, 값이 해당 타입의 트랩 표현이고 이를 CPU에 로드할 때 하드웨어 예외가 발생하는 경우(예: SNaN("signalling not-a-number") 부동소수점 값 또는 NaT("not-a-thing") 정수)는 제외).
대부분의 구현은 정수 타입에 대해 트랩 표현, 패딩 비트 또는 다중 표현을 허용하지 않지만 예외가 있습니다. 예를 들어 Itanium에서 정수 타입의 값은 트랩 표현일 수 있습니다.
역은 반드시 참이 아닙니다: TriviallyCopyable 타입의 두 객체가 서로 다른 객체 표현을 가지면서 동일한 값을 나타낼 수 있습니다. 예를 들어, 여러 부동소수점 비트 패턴이 동일한 특수 값 NaN을 나타냅니다. 더 일반적으로, 패딩 비트는 정렬 요구사항, 비트 필드 크기 등을 만족시키기 위해 도입될 수 있습니다.
#include <cassert>
struct S
{
char c; // 1 byte value
// 3 bytes of padding bits (assuming alignof(float) == 4)
float f; // 4 bytes value (assuming sizeof(float) == 4)
bool operator==(const S& arg) const // value-based equality
{
return c == arg.c && f == arg.f;
}
};
void f()
{
assert(sizeof(S) == 8);
S s1 = {'a', 3.14};
S s2 = s1;
reinterpret_cast<unsigned char*>(&s1)[2] = 'b'; // modify some padding bits
assert(s1 == s2); // value did not change
}
타입 char, signed char, unsigned char, char8_t(C++20부터) (과대 비트 필드가 아닌 경우)의 객체에 대해, 객체 표현의 모든 비트는 값 표현에 참여해야 하며 각 가능한 비트 패턴은 고유한 값을 나타냅니다(패딩 비트, 트랩 비트 또는 다중 표현 허용되지 않음).
하위 객체
객체는 하위 객체를 가질 수 있습니다. 여기에는 다음이 포함됩니다:
- 멤버 객체
- 기반 클래스 하위 객체
- 배열 요소
다른 객체의 하위 객체가 아닌 객체를 완전한 객체라고 합니다.
완전한 객체, 멤버 하위 객체 또는 배열 요소가 클래스 타입인 경우, 해당 타입은 기반 클래스 하위 객체의 클래스 타입과 구별하기 위해 최종 파생 클래스 로 간주됩니다. 최종 파생 클래스 타입 또는 비클래스 타입의 객체를 최종 파생 객체 라고 합니다.
클래스의 경우,
를 잠재적으로 생성되는 하위 객체라고 합니다.
크기
하위 객체는 기반 클래스 하위 객체이거나 속성(C++20부터)[[no_unique_address]]으로 선언된 비정적 데이터 멤버인 경우 잠재적으로 겹치는 하위 객체입니다.
객체 obj는 다음 모든 조건을 만족하는 경우에만 크기가 0일 수 있습니다:
obj는 잠재적으로 겹치는 하위 객체입니다.obj는 가상 멤버 함수와 가상 기반 클래스가 없는 클래스 타입입니다.obj에는 크기가 0이 아닌 하위 객체나 길이가 0이 아닌 이름 없는 비트 필드가 없습니다.
위 조건을 모두 만족하는 객체 obj에 대해:
- 가 비정적 데이터 멤버가 없는
obj표준 레이아웃 (C++11부터) 클래스 타입의 기반 클래스 하위 객체인 경우, 크기가 0입니다.그렇지 않으면, - 의 크기가 0인 상황은 구현 정의입니다.
obj자세한 내용은
빈 기반 클래스 최적화를 참조하십시오. 크기가 0이 아닌 비트 필드가 아닌 객체는 하나 이상의 바이트의 저장소를 차지해야 하며, 해당 하위 객체 중 하나라도 차지하는(전체 또는 일부) 모든 바이트를 포함합니다. 객체가 trivially copyable
또는 standard-layout(C++11부터) 타입인 경우 차지하는 저장소는 연속적이어야 합니다. 주소
객체가 비트 필드 또는 크기가 0인 하위 객체가 아닌 경우, 해당 객체의
주소는 객체가 차지하는 첫 번째 바이트의 주소입니다. 객체는 다른 객체를 포함할 수 있으며, 이 경우 포함된 객체는 전자 객체
내에 중첩됩니다. 객체 가 다른 객체 a 내에 중첩되어 있다면 다음 조건 중 하나가 충족됩니다:
b는
a의 하위 객체입니다.b가b에 저장소를 제공합니다.a에 대해- 가
c내에 중첩되고a가c내에 중첩되는 객체c가 존재합니다.b객체가 다음 객체 중 하나라면
잠재적으로 고유하지 않은 객체입니다:
- 문자열 리터럴 객체.초기화 리스트의
|
배열 타입의 템플릿 매개변수 객체 ( |
| 잠재적으로 고유하지 않은 객체의 하위 객체. |
- 겹치는
수명을 가진 두 개의 비트 필드가 아닌 객체에 대해: 다음 조건 중 하나가 충족되면 동일한 주소를 가질 수 있습니다:
- 하나가 다른 하나 안에 중첩되어 있습니다.
- 둘 중 하나가 크기가 0인 하위 객체이고, 그들의 타입이
- 유사하지 않습니다.둘 다 잠재적으로 고유하지 않은 객체입니다.
- 그렇지 않으면 항상 서로 다른 주소를 가지며 서로 분리된 바이트 저장소를 차지합니다.
- 다형적 객체
// character literals are always unique
static const char test1 = 'x';
static const char test2 = 'x';
const bool b = &test1 != &test2; // always true
// the character 'x' accessed from “r”, “s” and “il”
// may have the same address (i.e., these objects may share storage)
static const char (&r) [] = "x";
static const char *s = "x";
static std::initializer_list<char> il = {'x'};
const bool b2 = r != il.begin(); // unspecified result
const bool b3 = r != s; // unspecified result
const bool b4 = il.begin() != &test1; // always true
const bool b5 = r != &test1; // always true
하나 이상의 가상 함수를 선언하거나 상속하는 클래스 타입의 객체는 다형적 객체입니다. 각 다형적 객체 내에서 구현은 추가 정보를 저장합니다(모든 기존 구현에서 최적화되지 않는 한 하나의 포인터입니다). 이 정보는
가상 함수 호출과 RTTI 기능( 및 dynamic_cast)에서 객체가 생성된 타입을 런타임에 결정하는 데 사용되며, 객체가 사용된 표현식과 무관합니다.
typeid비다형적 객체의 경우 값의 해석은 객체가 사용된 표현식에 의해 결정되며 컴파일 타임에 결정됩니다.
이 코드 실행
#include <iostream>
#include <typeinfo>
struct Base1
{
// polymorphic type: declares a virtual member
virtual ~Base1() {}
};
struct Derived1 : Base1
{
// polymorphic type: inherits a virtual member
};
struct Base2
{
// non-polymorphic type
};
struct Derived2 : Base2
{
// non-polymorphic type
};
int main()
{
Derived1 obj1; // object1 created with type Derived1
Derived2 obj2; // object2 created with type Derived2
Base1& b1 = obj1; // b1 refers to the object obj1
Base2& b2 = obj2; // b2 refers to the object obj2
std::cout << "Expression type of b1: " << typeid(decltype(b1)).name() << '\n'
<< "Expression type of b2: " << typeid(decltype(b2)).name() << '\n'
<< "Object type of b1: " << typeid(b1).name() << '\n'
<< "Object type of b2: " << typeid(b2).name() << '\n'
<< "Size of b1: " << sizeof b1 << '\n'
<< "Size of b2: " << sizeof b2 << '\n';
}
엄격한 에일리어싱
Expression type of b1: Base1
Expression type of b2: Base2
Object type of b1: Derived1
Object type of b2: Base2
Size of b1: 8
Size of b2: 1
객체가 생성된 타입과 다른 타입의 표현식을 사용하여 객체에 접근하는 것은 많은 경우 정의되지 않은 동작입니다. 예외 및 예제 목록은
를 참조하십시오.
reinterpret_cast정렬
모든
객체 타입에는 정렬 요구사항이라는 속성이 있습니다. 이는 음이 아닌 정수 값(타입 std::size_t, 항상 2의 거듭제곱)으로, 이 타입의 객체가 할당될 수 있는 연속적인 주소 사이의 바이트 수를 나타냅니다. 타입의 정렬 요구사항은
|
또는 |
각 객체 타입은 해당 타입의 모든 객체에 정렬 요구사항을 부과합니다 |
; 더 엄격한 정렬(더 큰 정렬 요구사항)은 (C++11부터)alignas를 사용하여 요청할 수 있습니다. 객체 타입의 정렬 요구사항을 충족하지 않는 저장소에 객체를 생성하려고 시도하는 것은 정의되지 않은 동작입니다.
클래스의 모든 비정적 멤버의 정렬 요구사항을 충족시키기 위해 패딩 비트가 일부 멤버 뒤에 삽입될 수 있습니다. 이 코드 실행
#include <iostream>
// objects of type S can be allocated at any address
// because both S.a and S.b can be allocated at any address
struct S
{
char a; // size: 1, alignment: 1
char b; // size: 1, alignment: 1
}; // size: 2, alignment: 1
// objects of type X must be allocated at 4-byte boundaries
// because X.n must be allocated at 4-byte boundaries
// because int's alignment requirement is (usually) 4
struct X
{
int n; // size: 4, alignment: 4
char c; // size: 1, alignment: 1
// three bytes of padding bits
}; // size: 8, alignment: 4
int main()
{
std::cout << "alignof(S) = " << alignof(S) << '\n'
<< "sizeof(S) = " << sizeof(S) << '\n'
<< "alignof(X) = " << alignof(X) << '\n'
<< "sizeof(X) = " << sizeof(X) << '\n';
}
가장 약한 정렬(가장 작은 정렬 요구사항)은
alignof(S) = 1
sizeof(S) = 2
alignof(X) = 4
sizeof(X) = 8
, char, signed char의 정렬로, unsigned char와 같습니다; 스칼라 타입의 가장 큰 1기본 정렬은 구현 정의이며 std::max_align_t(C++11부터)의 정렬과 같습니다.
특정 컨텍스트에서 특정 확장 정렬에 대한 요청이 구현에 의해 지원되지 않으면 프로그램은 ill-formed입니다.
타입의 정렬이
|
std::max_align_t보다 더 엄격(더 큼)해지면 확장 정렬 요구사항이 있는 타입으로 알려집니다. 정렬이 확장된 타입 또는 비정적 데이터 멤버가 확장 정렬을 가진 클래스 타입은 과정렬 타입입니다. 할당자 유형은 과정렬 타입을 올바르게 처리해야 합니다. (C++11부터) |
|
표현식 |
(C++20까지) 참고 |
C++에서 객체는
객체 지향 프로그래밍(OOP)의 객체와 다른 의미를 가집니다: C++의 객체
| OOP의 객체 | 모든 객체 타입을 가질 수 있음 |
|---|---|
| ( std::is_object 참조) 클래스 타입이어야 함 |
"인스턴스" 개념 없음 |
| "인스턴스" 개념 있음 (그리고 | 와 같은 메커니즘으로 "instance-of" 관계를 감지 가능)
instanceof"인터페이스" 개념 없음
|
| "인터페이스" 개념 있음 (그리고 | 와 같은 메커니즘으로 인터페이스 구현 여부를 감지 가능)
instanceof다형성은 가상 멤버를 통해 명시적으로 활성화해야 함
|
| 다형성은 항상 활성화됨 | 결함 보고서 |
P0593R6에서, 바이트 배열을 생성하거나 할당 함수(사용자 정의 및 가능)를 호출할 때 상수 평가 중에 암시적 객체 생성이 발생하는 것으로 간주되었습니다. 그러나 이러한 허용은 상수 평가에서 비결정성을 초래하여 원하지 않았고 일부 측면에서 구현 불가능했습니다. 그 결과 constexprP2747R2는 상수 평가에서 이러한 암시적 객체 생성을 허용하지 않았습니다. 우리는 전체 논문이 아니더라도 이러한 변경을 결함 보고서로 의도적으로 처리합니다.
결함 보고서
다음 동작 변경 결함 보고서는 이전에 발행된 C++ 표준에 소급 적용되었습니다.
DR
| 적용된 표준 | 발행된 동작 | 올바른 동작 | CWG 633 |
|---|---|---|---|
| C++98 | 변수는 객체만 될 수 있음 | 참조도 될 수 있음 | CWG 734 |
| C++98 | 동일한 스코프에 정의된 변수 중 | 동일한 값을 가지는 것이 보장된 변수들이 동일한 주소를 가질 수 있는지 명시되지 않음 수명이 겹치는 경우 주소가 |
다름이 보장됨, 값과 무관 CWG 1189 |
| C++98 | 동일한 타입의 두 기반 클래스 하위 | 객체가 동일한 주소를 가질 수 있었음
항상 서로 다른 주소를 가짐 |
CWG 1861 |
| C++98 | 좁은 문자 타입의 과대 비트 필드에서 | 객체 표현의 모든 비트가 여전히 값 표현에 참여했음 패딩 비트를 허용함 |
CWG 2489 |
| C++98 | 은 저장소를 제공할 수 없지만, 객체가 | char[]해당 저장소 내에서 암시적으로 생성될 수 있었음
객체는 |
의 저장소 내에서 암시적으로 생성될 수 없음char[]
|
| CWG 2519 | C++98 | 객체 표현의 정의가 비트 필드를 다루지 않았음 | 비트 필드를 다룸 |
| CWG 2719 | C++98 | 잘못 정렬된 저장소에 객체를 생성하는 동작이 불명확했음 |
이 경우 동작은 정의되지 않음 |
| CWG 2753 | C++11 | 초기화 리스트의 지원 배열이 문자열 리터럴과 저장소를 공유할 수 있는지 불명확했음 |
저장소를 공유할 수 있음 |
| CWG 2795 | C++98 | 겹치는 수명을 가진 두 객체가 동일한 주소를 가질 수 있는지 결정할 때, 둘 중 하나가 크기가 0인 하위 객체인 경우, 유사하지만 다른 타입을 가질 수 있었음 |
비유사 타입만 허용함 |
| P0593R6 | C++98 | 이전 객체 모델은 표준 라이브러리에 필요한 많은 유용한 관용구를 지원하지 않았고 C의 효과적 타입과 호환되지 않았음 |
암시적 객체 생성 추가됨 |
같이 보기
C 문서 for 객체
|