때때로 객체를 단순하게 "plain old data" (메모리의 연속 byte sequence)로 취급하고 런타임 다형과 같은 고급 개념에 대해 걱정하지 않으려고 한다. 종종 그렇게 하는 이유는 하드웨어가 할 수 있는 가장 효율적인 객체 이동의 방법이기 때문이다. 예를 들어 복사 생성자의 100번 호출을 사용하여 100개의 요소 배열을 복사하는 것은 일반적으로 단순 블록 이동 명령을 사용하는 std::memcpy()를 호출하는 것만큼 빠르지 않을 것이다.
생성자가 inline 된 경우에도 optimizer가 최적화를 발견하기 어려울 수 있다. 이러한 "trick"은 드문 일이 아니며 vector와 같은 컨테이너 구현 및 저수준 I/O 루틴에서 중요하다. 그것들은 불필요하며 상위 레벨 코드에서는 피해야 한다.
따라서 POD("Plain Old Data")는 클래스 레이아웃의 복잡성이나 생성, 복사 및 사용자 정의 의미론에 대해 걱정하지 않고 그냥 데이터로 조작할 수 있는 객체다.
struct S0 { }; // a POD
struct S1 { int a; }; // a POD
struct S2 { int a; S2(int aa) : a(aa) { } }; // not a POD (no default constructor)
struct S3 { int a; S3(int aa) : a(aa) { } S3() {} }; // a POD (user-defined default constructor)
struct S4 { int a; S4(int aa) : a(aa) { } S4() = default; }; // a POD
struct S5 { virtual void f(); /* ... */ }; // not a POD (has a virtual function)
struct S6 : S1 { }; // a POD
struct S7 : S0 { int b; }; // a POD
struct S8 : S1 { int b; }; // not a POD (data in both S1 and S8)
struct S9 : S0, S1 {}; // a POD
객체를 POD로 조작하려면 객체는 반드시 다음과 같아야 한다.
- 복잡한 레이아웃이 없다. (예, vptr)
- 비표준(사용자 정의) 복사 의미 체계가 없다.
- 기본 생성자가 있다.
분명히, 언어 보장을 위반하지 않는 최적화만 사용하도록 POD의 정의는 정확해야 한다.
공식적으로 객체는 다음과 같아야 한다.
- 표준 레이아웃 유형
- 간단하게 복사할 수 있는 유형
- 기본 생성자가 있는 유형
관련 개념은 다음과 같은 사소한 유형이다.
- 기본 생성자
- 간단한 복사 및 이동 작업
비공식적으로 기본 생성자는 작업을 수행할 필요가 없는 경우 간단하다. ( = default를 사용 ) 유형은 다음을 제외한 표준 레이아웃을 갖는다.
- 비정적 멤버 또는 표준 레이아웃이 아닌 base가 있는 경우
- virtual 함수가 있는 경우
- virtual base 가 있는 경우
- 참조 유형의 멤버가 있는 경우
- 비정적 데이터 멤버에 대한 다중 액세스 지정자가 있는 경우
- 중요한 레이아웃 최적화 방지가 있는 경우
- 둘 이상의 기본 클래스 또는 파생된 클래스와 base 둘 다에 비정적 데이터 멤버가 있는 경우
- 첫 번째 비정적 데이터 멤버와 동일한 유형의 기본 클래스를 가지는 경우
기본적으로 표준 레이아웃 유형은 C에서 명백하게 동등한 레이아웃을 갖고 있으며 일반적인 C+ ABI(Application Binary Interface)가 처리할 수 있는 통합에 있다.
형식은 중요한 복사 작업, 이동 작업 또는 소멸자가 없는 한 간단히 복사할 수 있다. 비공식적으로 복사 작업은 비트 단위 복사로 구현할 수 있는 경우 간단하다. 그렇다면 무엇이 복사, 이동 또는 소멸자를 중요하지 않게 만들까?
- 사용자 정의
- 해당 클래스에 virtual function이 있음
- 해당 클래스에 virtual base가 있음
- 해당 클래스에 not trivial 한 base나 멤버가 있음
built-in 객체는 복사가 가능하고 표준 레이아웃을 가지고 있다. 또한 간단하게 복사할 수 있는 객체의 배열은 쉽게 복사할 수 있으며 표준 레이아웃 객체의 배열에는 표준 레이아웃이 있다.
template<typename T>
void mycopy(T∗ to, const T∗ from, int count);
T가 POD인 간단한 경우를 최적화하고 싶다면 POD에 대해 mycopy()를 호출하는 것만으로 그렇게 할 수 있지만 오류가 발생하기 쉽다. mycopy()를 사용하는 경우 POD가 아닌 경우에는 mycopy()를 호출하지 않도록 코드의 유지 관리자에게 의존할 수 있을까? 현실적으로 불가능하다. 또는 std::copy()를 호출할 수 있다. 이는 필요한 최적화로 구현될 가능성이 가장 높다.
template<typename T>
void mycopy(T∗ to, const T∗ from, int count)
{
if (is_pod<T>::value)
memcpy(to,from,count∗sizeof(T));
else
for (int i=0; i!=count; ++i)
to[i]=from[i];
}
is_pod()는 <type_traits>에 정의된 표준 라이브러리로, 코드에서 T 유형이 POD인지 알 수 있다. is_pod <T>의 가장 좋은 점은 POD가 무엇인지에 대한 정확한 규칙을 기억하지 않아도 된다는 것이다.
기본이 아닌 생성자를 추가하거나 빼도 레이아웃이나 성능에 영향을 미치지 않는다.(C++98 예외).
'Program Language > C & C++' 카테고리의 다른 글
[C++] Unions (0) | 2022.01.04 |
---|---|
[C++] 구조체의 필드(field) (0) | 2022.01.04 |
[C++] 구조체 타입 동등성 (2) | 2022.01.03 |
[C++] 구조체와 배열 (0) | 2022.01.03 |
[C++] 구조체와 클래스 (0) | 2022.01.03 |