codenuri 강석민 강사 강의 내용기반으로 정리한 내용입니다.
변환연산자와 변환생성자
#include <iostream>
using namespace std;
class Point
{
    int x, y;
public:
    Point() : x(0), y(0) {}
    Point(int a, int b) : x(a), y(b) {}
};
int main()
{
    int n = 3;
    double d = n; // 암시적 형변환 발생.
    Point p1(1, 2);
    n = p1; // p1.operator int()
    cout << n << endl;
}
p1.operator int 를 만들었다면 p1의 Point 가 int로 변환할 수 있음
-> 이런 것을 변환연산자
변환연산자
객체를 다른 타입으로 변환할 때 호출된다.
특징 : 리턴 타입을 표기하지 않는다.
#include <iostream>
using namespace std;
class Point
{
    int x, y;
public:
    Point() : x(0), y(0) {}
    Point(int a, int b) : x(a), y(b) {}
    int operator int()
    {
        return x;
    }
};
int main()
{
    int n = 3;
    double d = n; // 암시적 형변환 발생.
    Point p1(1, 2);
    n = p1; // p1.operator int()
    cout << n << endl; // 1
}
컴파일러가 n = p1을 보면 int 로 변환하려고 하는데 이를 변환 연산자를 통해 수행하려고 함
변환 생성자
#include <iostream>
using namespace std;
class Point
{
    int x, y;
public:
    Point() : x(0), y(0) {}
    Point(int a, int b) : x(a), y(b) {}
    int operator int()
    {
        return x;
    }
};
int main()
{
    int n = 3;
    Point p(1, 2);
    n = p;
    p = n;
}
n = p; 는 Point -> int 로 p.operator int()
p = n; 은 int -> Point 로 n.operator Point() 가 있으면 된다.
하지만 n은 사용자정의 타입이 아니다.
#include <iostream>
using namespace std;
class Point
{
    int x, y;
public:
    Point() : x(0), y(0) {}
    Point(int a, int b) : x(a), y(b) {}
    int operator int()
    {
        return x;
    }
};
int main()
{
    Point p1;
    Point p2(1,1);
    int n = 3;
    Point p(1, 2);
    n = p;
    p = n;
}
int가 2개 있으면 Point를 만들 수 있다. -> Point p2(1, 1);
int 가 하나도 없더라도 Point를 만들 수 있다 -> Point p1
p = n; 은 Point 생성자에 인자가 하나 있는 것을 만들어야함
#include <iostream>
using namespace std;
class Point
{
    int x, y;
public:
    Point() : x(0), y(0) {}
    Point(int a, int b) : x(a), y(b) {}
    // 인자가 한 개인 생성자 - 변환생성자
    Point(int a) : x(a), y(0) {} 
    int operator int()
    {
        return x;
    }
};
int main()
{
    Point p1;
    Point p2(1,1);
    int n = 3;
    Point p(1, 2);
    n = p;
    p = n;
}
변환생성자 : 다른 타입이 Point class로 변환 되게 한다.
생성자 중에서 인자가 하나 같는 생성자를 만들면 변환에 사용될 수 있다.
정 리
- Point -> int로 변환하려면 변환 연산자 p.operator int()
 - int -> Point 로 변환하려면 변환 생성자 Point(int)
 
변환의 장단점
변환의 장점
class OFile
{
    FILE* file;
public:
    OFile(const char* filename, const char* mode = "wt")
    {
        file = fopen(filename, mode);
    }
    ~OFile()
    {
        fclose(file);
    }
    // 변환연산자
    operator FILE*() {return file;}
};
int c_main()
{
    FILE* f = fopen("a.txt", "wt");
    fputs("hello", f);
    if (...)
        return false; // c에서는 이러면 해지도 해주어야함
    fclose(f);
}
int main()
{
    OFile f("a.txt"); // 생성자에서 자원을 할당하고, 소멸자에서 자원을 해제 => RAII
    // C 함수를 사용해서 파일 작업 -> C++로 자원관리 쉽게하고 C함수도 자유롭게 사용하게 해보자
    fputs("hello", f);
    fprintf(f, "n=%d", 10); // OFile => FILE*로 암시적 변환되면 가능.
    // f.operator FILE*() 변환연산자를 만들면됨
    // 대표 예제
    String s1 = "hello";
    char s2[10];
    strcpy(s2, s1); // String -> const char* 암시적 변환 필요
}
RAII : Resource Acquision Is Initialization
fclose 도 자동으로 하면서 기존의 모든 C 함수를 다시 다 쓸 수 있음
변환의 단점
class OFile
{
    FILE* file;
public:
    OFile(const char* filename, const char* mode = "wt")
    {
        file = fopen(filename, mode);
    }
    ~OFile()
    {
        fclose(file);
    }
    operator FILE*() {return file;}
};
void foo(OFile f) {}
int main()
{
    OFile f("a.txt");
    foo(f); // ok..
    foo("hello"); // error 발생하지 않음
}
hello는 정체가 const char*임
foo한테 넘어갈 때 error 가 안나는 것은 const char*가 OFile로 암시적 변환 발생
변환생성자로 변환할텐데, OFile의 생성자는 인자2개처럼 보이지만 2번째 생성자인 mode는 default 값이 존재하므로 변환생성자로 사용될 수 있음
잘못된 코드가 에러가 안나오는 문제가 있음
생성자로 인한 암시적 변환은 매우 위험함 -> explicit 생성자 이용
explicit 생성자
인자가 한개인 생성자가 암시적 변환을 허용하는 것을 막는다.
class OFile
{
    FILE* file;
public:
    explicit OFile(const char* filename, const char* mode = "wt")
    {
        file = fopen(filename, mode);
    }
    ~OFile()
    {
        fclose(file);
    }
    operator FILE*() {return file;}
};
void foo(OFile f) {}
int main()
{
    OFile f("a.txt");
    foo(f); // ok..
    // foo("hello"); // error
    foo(static_cast<OFile>("hello")); // 명시적 변환은 됨
}
explicit
explicit 와 객체의 초기화 기술
class Test
{
    int value;
public:
    explicit Test(int n) : value(n) {}
};
int main()
{
    // 아래 2줄의 차이점은 ?
    Test t1(5); // 인자가 한개인 생성자 호출 -> direct initialization
    Test t2 = 5; // copy initialization
}
Test t2=5 가 하는일
- 변환생성자를 사용해서 5를 가지고 Test 의 임시 객체 생성
 - 임시객체를 복사 생성자를 사용해서 t2에 복사
 
explicit 를 붙이면 Test t2=5 는 error 발생 -> =로 초기화 될 수 는 없다.
STL을 잠깐 보자
#include <string>
#include <memory>
using namespace std;
int main()
{
    // STL의 string 클래스는 생성자가 explicit 가 아님
    string s1("hello");  // ok
    string s2 = "hello"; // ok
    shared_ptr<int> p1 = new int; // error 생성자가 explicit
    shared_ptr<int> p2(new int);  // ok..
}
= 이 안되면 생성자가 explicit 로 되어 있다로 이해하면됨
make nullptr
int main()
{
    int n1 = 10;    // ok
    void* p1 = 10;  // error.
    int n2 = 0;     // ok
    void* p2 = 0;   // ok. 0은 정수지만 포인터로 암시적 형변환된다.
}
#include <iostream>
using namespace std;
void foo(int n) {cout << "int" << endl;}
void foo(void* p) {cout << "void*" << endl;}
void goo(char* p) {cout << "goo" << endl;}
int main()
{
    foo(0);
    foo((void*)0);
#define NULL (void*)0
    foo(NULL);
    goo(NULL); // void* -> char* 로의 암시적 변환 필요.
    // C: ok
    // C++: 암시적 변환 안됨
#ifdef __cpluscplus
    #define NULL 0
#else
    #define NULL (void*)0
#endif
}
C++에서 정수 0은 있는데 완벽한 pointer 0이 없어서 위와같은 혼란 발생
변환을 사용해서 pointer 0을 만들어보자
#include <iostream>
using namespace std;
void foo(int n) {cout << "int" << endl;} // 1
void foo(void* p) {cout << "void*" << endl;} // 2
void goo(char* p) {cout << "goo" << endl;} // 3
struct xnullptr_t
{
    // operator void*() { return 0; }
    template<typename T>
    operator T*() { return 0; } // 모든포인터의 타입을 암시적 형변환되어 goo 출력됨
};
xnullptr_t xnullptr; // 포인터 0
int main()
{
    foo(0); // 1
    foo(xnullptr); // 2. xnullptr_t -> void*로의 암시적 변환 필요
    // xnullptr.operator void*()
    goo(xnullptr); // 3 goo
    int n = 0;
    double* p = xnullptr; // double의 포인터로 변환됨
}
// C++ 11 : nullptr
int main()
{
    int n = 0;
    double* p1 = nullptr; // C++11 의 포인터 0
    nullptr_t a = nullptr;
    int* p = a;
}
nullptr 은 정확히 nullptr_t라는 데이터 타입의 변수임