일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- C++
- this call
- dynamic_cast
- vector capacity
- std::vector
- discord bot
- new&delete
- vector size
- delete function
- std::cout
- virtual function table
- member function pointer
- base from member
- pointer to member data
- placement new
- virtual function
- c++ basic practice
- virtual inheritance
- increment operator
- virtual destructor
- diamond inheritance
- 더 지니어스 양면포커
- constructor
- std::ostream
- operator overloading
- std::endl
- conversion constructor
- suffix return type
- return by reference
- c++ multi chatting room
- Today
- Total
I'm FanJae.
[C++ Intermediate] 객체의 변환(Conversion) 본문
1. 변환 연산자와 변환 생성자
1-1. 변환 연산자
#include <iostream>
class Int32;
{
int value;
public:
Int32() : value(0) { }
operator int() const { return value; }
};
int main()
{
int pn; // primitive type
Int32 un; // user type
pn = un; // un.operator int()
un = pn; // pn.operator Int32() 이는 불가능
// 1. un.operator=(pn)
// 2. Int32(pn)
}
- 객체가 다른 타입으로 변환 될 때 호출되는 함수이다.
operator TYPE()
{
return value;
}
- 반환 타입을 표기하지 않는다. 함수 이름에 반환 타입이 포함되어 있다.
※ 변환 연산자와 변환 생성자의 차이
- int pn, Int32 un 일 때
pn = un; | un.operator int(); 변환 연산자 객체(Int32) -> int로 변환될 때 |
un = pn; | pn.operator Int32() 는 만들 수 없다. ① 대입연산자, un.operator=(int) ② 변환생성자, Int32(pn) |
1-2. 변환 생성자
- 변환 생성자(인자가 한 개인 생성자)가 있다면 총 4개의 형태가 나온다.
#include <iostream>
class Int32
{
int value;
public:
Int32(int n) : value(n) { }
};
int main()
{
Int32 n1(3);
Int32 n2 = 3;
Int32 n3{3};
Int32 n4 = {3};
n1 = 3;
}
Int32 n1(3); | direct copy initialization |
Int32 n2 = 3; | copy initialization |
Int32 n3{3}; | direct copy initialization C++ 11 |
Int32 n4 = {3}; | copy initialization C++ 11 |
n1 = 3; | cconversion (int->Int32) |
Int32 n2 = 3;
- 이것이 C++ 14와 C++ 17을 기점으로 약간 다르다.
① C++14 까지 (Int32 n2 = 3; -> Int32 n2 = Int32(3);
- 인자가 한 개인 생성자를 사용해서 Int32 임시객체 생성
- 생성된 임시객체를 복사 생성자(C++98) 또는 move 생성자(C++11 이후)를 사용해서 n2에 복사(이동)
- 대부분 컴파일러가 최적화를 통해서 임시 객체 생성이 제거됨.
② C++17 이후
- 임시객체를 생성하지 않고, 인자 한 개인 생성자 호출
n1 = 3; -> n1 = Int32(3);
- 인자가 한 개인 생성자를 사용해서 Int32 임시객체를 생성한다.
- 생성된 임시객체를 디폴트 대입 연산자를 사용해서 n1에 대입한다.
- 대부분 컴파일러 최적화를 통해서 임시객체 생성이 제거된다.
- 디폴트 대입연산자가 삭제된 경우는 컴파일 에러가 발생한다.
#include <iostream>
class Int32
{
int value;
public:
Int32(int n) : value(n) { }
Int32& operator=(const Int32&) = delete;
};
int main()
{
Int32 n1(3);
Int32 n2 = 3;
Int32 n3{3};
Int32 n4 = {3};
n1 = 3;
}
- 이와 같은 경우 디폴트 대입 연산자의 삭제로 컴파일 에러가 발생한다.
2. Explicit 생성자
class Vector
{
public:
explicit Vector(int size) {}
};
void foo(Vector v) {} // Vector v = 3;
int main()
{
Vector v1(3);
Vector v2 = 3; // error
Vector v3{ 3 };
Vector v4 = { 3 }; // error
v1 = 3; // error
foo(3); // error
}
- 생성자가 암시적 변환의 용도로 사용될 수 없게 한다.
- 직접(Direct) 초기화 만 가능하고 복사(Copy) 초기화도 사용할 수 없다.
- 클래스에 따라서 Explicit를 사용할지 판단을 잘 해야 한다.
void f1(Int32 n)
{
}
f1(3);
void f2(Vector v)
{
}
f2(3);
- explicit를 붙일지 말지에 대한 여부를 잘 판단하여 붙여야 한다.
2-1. Explicit 변환 연산자
- 객체의 유효성을 if 문으로 조사하고 싶다.
#include <iostream>
class Machine
{
int data = 10;
bool state = true;
public:
};
int main()
{
Machine m;
if (m)
{
}
}
- 객체의 유효성을 if 문으로 조사하고 싶으면, Machine을 bool로 변환할 수 있으면 된다는 의미이다.
#include <iostream>
class Machine
{
int data = 10;
bool state = true;
public:
};
int main()
{
Machine m;
if (m)
{
}
}
- 이와 같이 operator bool()을 제공하면 된다.
- 하지만, 이는 side effect가 많다.
2-1-1. 예제의 문제점
#include <iostream>
class Machine
{
int data = 10;
bool state = true;
public:
operator bool() { return state; }
};
int main()
{
Machine m;
bool b1 = m; // ok
bool b2 = static_cast<bool>(m); // ok
m << 10;
if (m)
{
}
}
m << 10;
- 여기서 이 부분은 시프트 연산이다. m이 bool로 변환되기 때문에 이는 곧 정수로 인식한다.
2-1-2. 문제 해결법
① Explicit operator bool()
- C++11 부터 생성자 뿐 아니라 변환 연산자도 explicit를 붙일 수 있다.
- bool로의 암시적 변환은 허용되지 않는다.
- if 문 안에서는 사용될 수 있다.
- safe bool이라고도 한다.
#include <iostream>
class Machine
{
int data = 10;
bool state = true;
public:
explicit operator bool() { return state; }
};
int main()
{
Machine m;
bool b1 = m; // ok
bool b2 = static_cast<bool>(m); // ok
m << 10;
if (m)
{
}
}
2-1-3. C++ 버전과 Explicit
explicit 변환 생성자 | C++98 부터 제공되는 문법 |
explicit 변환 연산자 | C++11 부터 제공되는 문법 |
explicit(bool) | C++20 부터 제공되는 문법 |
2-2. explicit(bool) // C++20
#include <iostream>
#include <type_traits>
template<class T>
class Number
{
T value;
public:
explicit(!std::is_integral_v<T> ) Number(T v) : value(v) {}
};
int main()
{
Number n1 = 10;
Number n2 = 3.4;
}
explicit(!std::is_integral_v<T>)
- 이렇게 처리할 경우, 정수일때만 explicit를 허용하지 않겠다는 의미가 된다.
- 즉 explicit(조사식) 과 같은 형태로 사용할 수 있는 방식이다.
3. 변환 예제
3-1. nullptr
- 널 포인터를 의미한다.
- 포인터 초기화 시 0을 사용하지 말고 nullptr를 사용하라.
- nullptr의 타입은 std::nullptr_t이다.
void foo(int* p) { }
void goo(char* p) {}
struct nullptr_t
{
template <class T>
constexpr operator T* () const {
return 0;
}
};
nullptr_t xnullptr;
int main()
{
foo(xnullptr); // xnullptr.operator int*()
goo(xnullptr); // xnullptr.operator char*()
}
- 과거에는 이런 방식으로 만들었다.
3-2. return type resolver
#include <iostream>
/*
template<class T>
T* Alloc(std::size_t sz)
{
return new T[sz];
}
*/
struct Alloc
{
std::size_t size;
Alloc(std::size_t sz) : size(sz) { }
template<class T>
operator T*() { return new T[size]; }
};
int main(void)
{
// int* p1 = Alloc<int>(10);
// double* p2 = Alloc<double>(10);
int* p1 = Alloc(10); // 임시객체.operator int*()
double* p2 = Alloc(10); // 임시객체.operator int*()
}
- <int>, <double> 등을 매번 명시해줘야 해서 다소 번거로운데 이를 생략하고 사용할 수 없을까?
- 좌변을 보고 우변의 반환 타입을 자동으로 결정하는 기술이다.
3-3. 람다 표현식과 변환
int main()
{
auto f1 = [](int a, int b){ return a + b; };
int (*f2)(int, int) = [](int a, int b){return a+ b;};
// 임시객체
// 임시객체.operator 함수포인터()
- 람다 표현식 자체가 임시객체를 만든다.
- 따라서, 이는 임시 객체가 함수객체로 들어가는 과정이다. 이에 따라서 임시객체.operator 함수포인터() 형태이다.
'C++ > Intermediate' 카테고리의 다른 글
[C++ Intermediate] Member Function Pointer (2) | 2024.09.23 |
---|---|
[C++ Intermediate] this call (1) | 2024.09.22 |
[C++ Intermediate] Trivial (4) | 2024.09.19 |
[C++ Intermediate] new/delete, placement new (0) | 2024.09.18 |
[C++ Intermediate] Constructor 생성 원리 (1) | 2024.09.10 |