«   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-20 10:05
관리 메뉴

lancelot.com

conversion 본문

프로그래밍

conversion

lancelot50 2022. 8. 3. 21:46
  • 변환연산자 : 객체가 다른 타입으로 변환될 때 호출되는 함수
  • 함수이름에 반환이 있기때문에 반환 타입을 표기하지 않는다
operator TYPE() const // 상수 멤버함수로 만드는 경우가 많다
{
	return 값;
}

 

  • 변환 연산자 vs 변환 생성자
    • int pn, Int32 un 일때
      • pn=un; ==>: un.operator int();
        • 변환연산자
        • 객체(Int32) => Int로 변환될 때
      • un=pn; ==> pn.opertor Int32() 는 만들 수 없다. 
        • 대입연산자, un.operator=(int) 가 가능한지 찾아보고
        • 변환생성자, Int32(pn) 이 가능한지 찾는다.
#include<iostream>

class Int32
{
	int value;
public :
	Int32() : value(0) {}
	Int32(int val) : value(val)	{ }		// 변환생성자

	// 변환 연산자 : 객체가 다른 타입으로 변환될 때 호출되는 함수
	// operator TYPE()
	// {
	//		return 값
	// }
	// -> 반환타입을 표기하지 않는다. 함수이름에 이미 포함되어있기때문.
	operator int() const
	{
		return value;
	}

	void operator=(int val)
	{
		value = val;
	}
};

int main()
{
	int pn;
	Int32 un;

	pn = un;// un.operator int()
	un = pn;// pn.operator int32() 는 만들 수 없다
			// 1. un.operator=(pn) 이 가능한지 찾고
			// 2. Int32(pn) 이 가능한지 찾는다
}

 

  • 변환생성자(인자가 한 개인 생성자)가 있다면
Int32 n1(3); // direct initialization
Int32 n2=3;  // copy initialization
Int32 n3{3}; // direct initialization  : C++11
Int32 n4={3} // copy initialization    : C++11
n1=3;		// conversion (int -> Int32)

 

  • Int32 n2 = 3;   ==>  Int32 n2 = int32(3);
    • C++14까지
      1. 인자가 한 개인 생성자를 사용해서 "Int32 임시객체 생성"
      2. 생성된 임시객체를 "복사생성자(C++98)" 또는 "move 생성자(C++11이후)" 를 사용해서 n2에 복사(이동)
        • 대부분의 컴파일러가 최적화를 통해 "임시객체 생성이 제거"됨.
    • C++17 이후
      • 임시객체를 생성하지 않고, 인자 한 개인 생성자 호출 ( 복사생성자를 사용하지 않음)
#include<iostream>

class Int32
{
	int value;
public :
	Int32(int n) : value(n) {}
	Int32(const Int32&) = delete;// VS2022 에서는 /Std:C++14 해도 잘됨 -_-;
    Int32& operator=(const Int32&) = delete;
};

int main()
{
	Int32 n1(3);
	Int32 n2 = 3;
	Int32 n3{ 3 };
	Int32 n4 = { 3 };
	n1 = 3;
}
  • n1=3;   ==> n1=Int32(3);
    1. 인자가 한 개인 생성자를 사용해서 Int32 임시객체 생성
    2. 생성된 임시객체를 디폴트 대입 연산자를 사용해서 n1에 대입
      • 대부분 컴파일러가 "최적화를 통해 임시객체 생성이 제거"됨.
      • 디폴트 대입연산자가 삭제된 경우 "컴파일 에러" 발생

 

  • explicit 생성자
    • 생성자가 암시적 변환의 용도로 사용될 수 없게 한다.
    • 직접 초기화 ( direct initialization ) 만 가능하고 복사 초기화 ( copy initialization ) 도 사용할 수 없다.
class Vector
{
public :
	// explicit 생성자
	// -> 생성자가 암시적 변환의 용도로 사용될 수 없게한다
	// 직접 초기화 ( direct initialization ) 만 가능하고 복사 초기화 ( copy initialization ) 도 사용할 수 없다.
	explicit Vector(int size) {}
};

void foo(Vector v) {}

int main()
{
	Vector v1(3);
	Vector v2 = 3;	// error
	Vector v3{ 3 };
	Vector v4 = { 3 };	// error

	v1 = 3;		// error

	foo(3);		// error
}

 

  • explicit 변환 연산자
    • 객체의 유효성을 if 문으로 조사하고 싶다
      • operator bool() 을 제공하면 된다
      • operator bool() 은 side effect 가 많다
    • explicit operator bool()
      • C++11 부터 생성자 뿐 아니라 "변환 연산자도 explicit 를 붙일 수 있다"
      • bool 로의 암시적 변환은 허용되지 않음
      • if문안에서는 사용가능
      • "safe bool"
  • C++ 버전과 explicit
    • explicit 변환 생성자 : C++98
    • explicit 변환 연산자 : C++11
    • explicit(bool) : C++20
class Machine
{
	int data = 10;
	bool state = true;
public :
	explicit operator bool() { return state; }
};

int main()
{
	Machine m;
	bool b1 = m;	// error
	bool b2 = static_cast<bool>(m);

	m << 10;		// error

	if (m)
	{
	}
}

 

  • explicit(bool) : C++20
    • 조건에 따라 explicit의 여부를 결정함
#include<iostream>
#include<type_traits>

template<class T>
class Number
{
	T value;
public :
	// C++20 부터 가능 - explicit(bool)
	explicit(!std::is_integral_v<T>) Number(T v) : value(v){}
};

int main()
{
	// C++17 부터 template의 타입 생략 가능
	Number n1 = 10;	// ok
	Number n2 = 3.4;// error
}

 

  • nullptr
    • null pointer를 의미
    • 포인터 초기화시 0을 사용하지 말고 nullptr을 사용해라.
    • boost 라이브러리에 있는 도구를 C++11을 만들면서 표준에 추가한 것 ( 현재는 C++ keyword )
  • nullptr 의 타입
    • std::nullptr_t
void foo(int* p) {}
void goo(char* p) {}

struct nullptr_t
{
	template<typename T>
	constexpr operator T* () const { return 0; }
};

nullptr_t xnullptr;

int main()
{
	foo(xnullptr);
	goo(xnullptr);
}

 

  • return type resolver - 좌변을 보고 우변의 반환 타입을 자동으로 결정하는 테크닉
#include<iostream>

template<typename T>
T* Allocation(std::size_t sz)
{
	return new T[sz];
}

// 위의 Allocation 의 경우는 type을 생략할 수 없다.
// 타입을 생략하려면? - return type resolver
struct Alloc
{
	std::size_t size;
public:
	Alloc(std::size_t sz) : size(sz) {}
	template<typename T>
	operator T*() { return new T[size]; }
};

int main()
{
	int* p1 = Allocation<int>(10);
	double* p2 = Allocation<double>(10);

	int* p3 = Alloc(10);		// 임시객체.operator int*()
	double* p4 = Alloc(10);
}

 

  • 람다표현식과 변환
int main()
{
	auto f1 = [](int a, int b) { return a + b; };// 보통은 이렇게 auto 변수에 대입

	// 함수포인터에도 대입가능
	//  -> 람다 표현식 부분에 임시객체가 생성
	//  -> 임시객체.operator 함수포인터() 함수가 불리는 것
	//  -> 컴파일러가 생성하는 임시객체에는 operator 함수포인터() 가 자동으로 만들어짐
	int(*f2)(int, int) = [](int a, int b) { return a + b; };
}