«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Archives
Today
Total
04-19 20:46
관리 메뉴

lancelot.com

trivial 본문

프로그래밍

trivial

lancelot50 2022. 8. 13. 14:41
  • Special member function
    • 사용자가 제공하지 않으면 "컴파일러가 제공하는 멤버 함수"
      • 디폴트 생성자 ( default constructor )
      • 소멸자 ( destructor )
      • 복사 생성자 ( copy constructor )
      • 복사 대입연산자 ( copy assignment )
      • 이동 생성자 ( move constructor )
      • 이동 대입연산자 ( move assignment )
    • Special member function 들이 "trivial 하다"고 하는 경우의 의미는?
    • Trivial 의 조건
      • 컴파일러가 생성하는 Special member function 이 다음과 같은 경우
        • default constructor : 아무일도 하지 않을 때 ( no action perform )
        • destructor : 아무일도 하지 않을 때 ( no action perform )
        • copy constructor : 객체를 memmove( 또는 memcpy ) 로 복사하는 경우와 동일할때 ( bitwise copy )
        • copy assignment : 객체를 memmove( 또는 memcpy ) 로 복사하는 경우와 동일할때 ( bitwise copy ) 
        • move constructor : std::is_trivially_constructible<T, T&&>
        • move assingment : std::is_trivially_assignable<T, TT&>
    • Trivial Default Constructor
            • Trivially default constructor
              1. "컴파일러가 생성하는 디폴트 생성자" 가
              2. "아무일도 하지 않은 경우. ( no action perform )"
                • 다음의 모든 조건을 만족해야 한다.
                  1. The constructor is not user-provided
                  2. T has no virtual member functions
                  3. T has no virtual base classes
                  4. T has no non-static members with default initializers
                  5. Every direct base of T has a trivial default constructor
                  6. Every non-static member of class has a trivial default constructor
            • 조사하는 방법 - type_traits 사용
      std::is_trivially_default_constructible<T>::value  // C++11
      std::is_trivially_default_constructible_v<T>	// C++17
#include<iostream>
#include<type_traits>

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

struct TrivialDefaultConstructor
{
	int data;
};

struct NonTrivialDefaultConstructor
{
	int data;
	NonTrivialDefaultConstructor() {}
};

struct Type1
{
	Type1(int a) {}	// 인자를 받는 생성자가 있으므로, 컴파일러가 디폴트 생성자를 제공하지 않음 - false
};

struct Type2			
{
	Type2() {}			// false
	Type2(int a) {}
};
struct Type3
{
	Type3() = default;	// true
	Type3(int a) {}
};
struct Type4			// false
{
	int data=0;

	// 컴파일러가 생성하는 코드 : default생성자를 만들지만, data를 초기화 함 - non trivial
	// int data;
	// Type() : data(0) {}
};

struct Type5
{
	int data;
	virtual void foo() {}// 가상함수가 있으면 default생성자에서 가상함수 테이블을 초기화함 - non trivial
};

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

	// Type6() : data2() {} // NonTrivial memeber이기때문에 컴파일러가 추가한 코드 - non trivial
};

struct Type7	// true
{
	int data1;
	TrivialDefaultConstructor data2;
	// Type7() : data2() {} // Trivial memeber이기때문에 - trivial
};

struct Type8 : public NonTrivialDefaultConstructor	// false
{
	int data1;
};

struct Type9 : public TrivialDefaultConstructor	// true
{
	int data1;
};

struct Type10 : public virtual TrivialDefaultConstructor	// false
{
	int data1;
	// 가상상속을 하면, default 생성자에서 가상상속을 위한 초기화 구문이 필요
};

struct Type11
{
	Type11() = delete;		// false
};

struct Type12		// false
{
	int& ref;	// 참조 멤버가 있으면 default 생성자는 =delete 됨. - non-trivial
				// 그래서 instance를 생성하면, 삭제된 생성자 참조로 error
};

struct Type13
{
	const int c;// 상수 멤버가 있으면 생성자는 =deletel됨 - non-trivial
};

int main()
{
	check<TrivialDefaultConstructor>();		// true
	check<NonTrivialDefaultConstructor>();	// false
	check<Type1>();
	check<Type2>();
	check<Type3>();
	check<Type4>();
	check<Type5>();
	check<Type6>();
	check<Type7>();
	check<Type8>();
	check<Type9>();
	check<Type10>();
	check<Type11>();
	check<Type12>();
	check<Type13>();
}

 

    • trivial copy constructor
      • 컴파일러가 생성한 복사 생성자가
      • 사용자가 직접 객체를 "memmove"등으로 복사하는 것과 동일할 때.
"effectively copies every scalar subobject ( including, recursively, subobject of jubobjects and so forth ) of the argument and performs no other action"

#include<iostream>
#include<type_traits>

class Point
{
	int x;
	int y;
public:
	Point() = default;
	Point(int a, int b) : x(a), y(b) {}

};

int main()
{
	Point pt1(1,2);
	Point pt2=pt1;

	Point pt3;
	memmove(&pt3, &pt1, sizeof(Point));
	std::cout <<std::boolalpha<< std::is_trivially_copy_constructible_v<Point> << std::endl;
}

 

  • trivially copy constructor
    • 다음의 모든 조건을 만족해야 한다.
      1. It is not user-provided ( that is, it is implicitly-defined or defaulted )
      2. T has no virtual member functions
      3. T has no virtual base classes
      4. the copy constructor selected for every direct base of T is trivial
      5. the copy constructor selected for every non-static class type ( or array of class type ) member of T is trivial
    • 조사하는 방법 - type_traits 사용
std::is_trivially_copy_constructible<T>::value // C++11
std::is_trivially_copy_constructible_v<T>	// C++17
  • trivial copy constructor 가 필요한 경우

배열의 복사

  • T의 복사 생성자가 trivial 하다면
    • 배열 전체를 memcpy 나 memmove 등으로 복사하는 것이 빠르다.
  • T의 복사생성자가 trivial 하지 않다면
    • 배열의 모든 요소에 대해 하나씩 "복사 생성자"를 호출해서 복사 해야 한다.
    • "placement new" 또는 "std::construct_at" 사용
#include<iostream>
#include<type_traits>

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

template<typename 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 constructor" << std::endl;
		while (sz--)
		{
			new(dst) T(*src);
			++dst;  ++src;
		}
	}
}

int main()
{
	Point arr1[5];
//	Point arr2[5];

	Point* arr2 = static_cast<Point*>(operator new(sizeof(Point) * 5));

	copy_type(arr1, arr2, 5);

	for (int i = 0; i < 5; ++i)
		arr2[i].~Point();

	operator delete(arr2);
}