C++ 중급 - Trivial Standard Layout

타입·2024년 2월 18일
0

C++ 공부

목록 보기
9/17

Trivial

  • Special Member Function
    사용자가 제공하지 않으면 컴파일러가 제공하는 멤버 함수
    • 디폴트 생성자(default constructor)
    • 소멸자(destructor)
    • 복사 생성자(copy constructor)
    • 복사 대입연산자(copy assignment)
    • 이동 생성자(move constructor)
    • 이동 대입 연산자(move assignment)
  • Trivial 조건
    컴파일러가 생성하는 special member function이 다음과 같은 경우
    • default constructor, destructor
      아무 일도 하지 않을 때
    • copy constructor, copy assignment
      객체를 memmove(또는 memcpy)로 복사하는 경우와 동일
    • move constructor, move assignment
      std::is_trivially_constructible<T, T&&>
      std::is_trivially_assignable<T, T&&>

Trivial Default Constructor

  1. 컴파일러가 생성하는 디폴트 생성자가
  2. 아무 일도 하지 않은 경우 (no action perform)
#include <type_traits>

struct TrivialDefaultCtor // true	
{
	int data;
};

struct NonTrivialDefaultCtor // false
{
	int data;
	NonTrivialDefaultCtor() {} // 사용자가 만든 디폴트 생성자
};

struct Type1 // false
{
	// 컴파일러가 디폴트 생성자 제공안함
	Type1(int a) {}
};

struct Type2 // false
{
	Type2() {};
	Type2(int a) {}
};

struct Type3 // true
{
	Type3() = default;
	Type3(int a) {}
};

struct Type4 // false
{
	int data = 0;

//	int data;
//	Type4() : data(0) {}
};

struct Type5 // false
{
	int data;
	virtual void foo() {} // 가상 함수 테이블 초기화
};

struct Type6 // false 
{
	int data1;
	NonTrivialDefaultCtor data2;

//	Type6() : data2() {} // 컴파일러가 추가한 코드
};

struct Type7 // true
{
	int data1;
	TrivialDefaultCtor data2;
//	Type7() : data2() {} // 생성자가 하는 일 없음
};

struct Type8 : public NonTrivialDefaultCtor // false
{
	int data;
    // 기반 클래스의 생성자를 호출
};

struct Type9 : public TrivialDefaultCtor // true
{
	int data;
};

struct Type10 : public virtual TrivialDefaultCtor // false
{
	// 가상 상속을 받으면 디폴트 생성자에서 가상 상속을 위한 초기화 필요
};

struct Type11 // false
{
	Type11() = delete; // 디폴트 생성자가 없음
};

struct Type12 // false
{
	int& ref; // 참조 멤버가 있으면 디폴트 생성자는 delete됨
};

struct Type13 // false
{
	const int c; // 상수 멤버가 있어도 디폴트 생성자는 delete됨
};

template<class T> void check()
{
	std::cout << typeid(T).name() << " : ";
	std::cout << std::boolalpha;
	std::cout << std::is_trivially_default_constructible_v<T> << std::endl;
};

디폴트 생성자가 Trivial하다면 생성자를 부를 필요가 없음
(어셈블리 단계에서도 디폴트 생성자를 호출하지 않음)


Trivial Copy Constructor

컴파일러가 생성한 복사 생성자가
사용자가 직접 객체를 memmove 등으로 복사하는 것과 동일할 때

  1. 복사 생성자를 사용자가 만들지 않고
  2. 가상 함수가 없고
  3. 가상 상속을 하지 않고
  4. 기반 클래스가 없거나 기반 클래스의 복사 생성자가 Trivial하고
  5. 멤버 데이터의 복사 생성자가 Trivial 함
#include <type_traits>

class Point
{
	int x;
	int y;
//	std::string s; // 동적 메모리 할당을 위해 복사 생성자를 만듦, Trivial하지 않음
public:
	Point() = default;
	Point(int a, int b) : x(a), y(b) {}

//	virtual void foo(){} // 가상 함수 테이블, Trivial하지 않음
};

int main()
{
	std::cout << std::is_trivially_copy_constructible_v<Point> << std::endl;

	Point pt1(1,2);
	Point pt2 = pt1;

	Point pt3;
	memmove(&pt3, &pt1, sizeof(Point));
}

컴파일러가 생성한 복사 생성자로 모든 멤버를 복사하는 것 외에는 아무 일도 하지 않음

  • Trivial 복사 생성자 활용
    T의 복사 생성자가 trivial 하다면 배열의 요소를 memcpy나 memmove 등으로 메모리 복사만 하여 빠름
    그렇지 않으면 모든 요소에 복사 생성자를 호출해서 복사해야하여 느림
#include <type_traits>

struct Point
{
	int x = 0;
	int y = 0;
};

template<class T> 
void copy_type(T* dst, T* src, std::size_t sz)
{
	if constexpr ( std::is_trivially_copy_constructible_v<T> )
	{
		std::cout << "using memcpy" << std::endl; // 메모리 복사
		memcpy(dst, src, sizeof(T)*sz);
	}
	else 
	{
		std::cout << "using copy ctor" << std::endl; // 복사 생성자
		while(sz--)
		{
			new(dst) T(*src); // placement new
			++dst, ++src;
		}	
	}
}
int main()
{	
	Point arr1[5];
	Point* arr2 = static_cast<Point*>( operator new(sizeof(Point) * 5) );

	copy_type(arr1, arr2, 5);

	// 메모리 해제
}
profile
주니어 언리얼 프로그래머

0개의 댓글