bdfgdfg
[C++ 11] auto,nullptr 본문
auto
C++11에서 등장한 문법이며 변수의 자료형을 컴파일 타임에 추론하여 자료형을 결정한다.
-> 그렇기에 auto 변수를 반드시 초기화 해야 된다. (동적(런타임)으로 자료형이 결정되지 않으므로)
-> 런타임에 결정되는게 아니므로 런타임 성능에 영향x
auto를 사용하여 포인터와 참조를 받을수도 있다.
-> 포인터를 받을 때 선언 : auto 혹은 auto*
-> 참조를 받을 때 선언 : auto&
포인터는 기본 auto로 받아도 되고 auto*로도 받을 수 있다.
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
|
class Test
{
public:
Test(std::string& value) : m_str(value) {}
~Test() { }
public:
void Print() { std::cout << m_str << std::endl; }
private:
std::string m_str;
};
int main()
{
std::string s("강남콩");
Test* test = new Test(s);
auto auto1 = test;
auto* auto2 = test;
auto1->Print();
auto2->Print();
std::cout << auto1 << " " << auto2;
delete test;
return 0;
}
|
cs |
실행 결과.
둘 다 같은 포인터 주소값을 받았고, 똑같이 동작한다는 것을 알 수 있다.
값형과 포인터값을 둘 다 일반 auto 선언으로 받을 수 있는 이유는 컴파일러가 어느형인지 추론이 가능하기 때문.
하지만 가독성면에서는 어떨까.
1
|
auto address = net.GetAddress();
|
cs |
코드만 봐서는 저 함수의 반환값이 값형인지 포인터값일지 알 수 없다.
-> 즉 가독성에 좋지 않다. 포인터형을 받을때는 auto*를 쓰는게 좋다.
이제 포인터가 아닌 참조를 보자. 참조는 일반 auto로 받을 수 없고 auto&로 받아야 한다.
1
2
3
4
|
std::string str("Test");
Test t1(str);
Test& myTest = t1;
auto myAuto = myTest;
|
cs |
주소를 확인해보면
myAuto의 주소는 888인것을 알 수 있다.
(참조는 그 메모리에 별칭을 붙이는 것. 참조로 받았다면 당연히 주소가 같아야 한다)
즉 myAuto는 t1객체의 메모리에 별칭(참조)을 붙인게 아닌, t1 객체를 복사한 것.
이제 참조 키워드를 붙여본다.
1
2
3
4
|
std::string str("Test");
Test t1(str);
Test& myTest = t1;
auto& myAuto = myTest;
|
cs |
이제는 모두 같은 주소를 가진다.
왜 auto&로만 참조가 가능하는지를 알아야 한다.
-> 참조는 선언과 동시에 초기화. 즉 별칭을 바로 붙여야 한다. 별칭을 붙인 후에는 다른 별칭으로 변경 불가능.
-> 일반 auto로 참조도 받을 수 있다면 받는게 참조인지 아닌지 컴파일러가 구분하기 힘들다.
1
|
auto a = b; // 참조? 복사?
|
cs |
-> 만약 가능했다 하더라도 가독성면에서도 좋지않다.
참고로 const또한 일반 auto로 받을 수 있다. -> 컴파일러가 추론가능하기에.
다만 이거또한 포인터형을 받을 때 처럼 가독성을 지키기 위해 const auto키워드를 붙여서 사용을 권장.
auto는 매우 편리하지만, 반대로 가독성을 무너뜨린다는 명확한 단점이 존재한다.
다만 몇가지 예외가 존재하는데, auto를 쓰면 매우 편리하고 그 예외의 상황에서는 auto라 할지라도 읽기 쉬운 코드.
-> 바로 auto와 반복자(iterator)
1
2
3
4
5
|
//기존의 list 순회
for (std::list<int>::const_iterator it = list.begin(); it != list.end(); ++it)
{
// 출력하는코드.
}
|
cs |
기존의 STL 컨테이너의 요소를 가리키는 방법.
이것을 auto를 이용하면
1
2
3
4
5
|
//auto(C++11)의 list(STL) 순회
for (auto it = list.begin(); it != list.end(); ++it)
{
// 출력하는코드.
}
|
cs |
코드가 매우 간단해지면서 가독성면에서도 나쁘지 않다.
-> begin과 end를 보고 컨테이너의 요소를 순회한다는것이 짐작가능
-> 다른 모든 컨테이너들도 마찬가지.
auto로 템플릿형도 받을 수 있다.
정리.
1. auto는 편리하지만 남발하지않고 실제 자료형타입 사용.
-> 반복자와 같은 케이스는 예외.
2. 굳이 쓰게된다면 포인터형을 받을 때 auto보다는 auto*를 사용한다는것을 기억 (가독성을 위해)
3. 참조는 일반 auto로 불가능하며 auto&. 보통 참조는 const와 붙어다니는게 일반적이므로 const auto&사용도 권장.
nullptr
- C++11에서 추가된 키워드. 포인터를 NULL로 초기화하기 위한 것.
기존의 C스타일은 포인터 값을 비워진 상태. 혹은 아무것도 가르키지 않는 상태를 의미하는 NULL 상수를 사용.
NULL의 가장 큰 문제점은 '0'이라는 정수형 상수값과 널 포인터 상수 두가지 의미를 동반했음.
-> 어떻게 보면 NULL은 개발자를 위해 가독성을 높인 꼼수같은 것.
그것을 해결하기 위해 나온게 nullptr. nullptr은 무조건 포인터 변수에만 대입이 가능한 값.
즉 이제는 NULL과 같이 두가지 의미를 동반하여 잠재적인 위험을 제거하고 NULL 포인터 상수의 기능만을 가짐.
실제로 이게 얼마나 편리한지 보자. 먼저 위에서 배운 auto를 통해 매크로 NULL과 nullptr의 추론 자료형을 보면
컴파일러는 NULL을 대상으로 int형으로 자료형을 추론했고, nullptr의 경우 std::nullptr_t라는 자료형으로 추론.
-> std::nullptr_t는 기존의 자료형을 래핑한게 아닌 새로운 자료형. 이게 굳이 무엇인지는 알 필요는 없다.
이제 nullptr은 확실하게 포인터 변수에만 대입가능한 포인터 상수란것을 알 수 있다.
기존의 NULL의 한계점은 간단하게 확인할 수 있는데
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
int GetPhoneNumber(const char* name)
{
std::cout << "const char* name호출" << std::endl;
return 0; }
int GetPhoneNumber(int id)
{
std::cout << "int id호출" << std::endl;
return 0; }
int main()
{
GetPhoneNumber(NULL);
return 0;
}
|
cs |
어느것을 호출할까? -> NULL은 정수 0.
int형 인자를 가진 함수를 호출한다.
nullptr을 넣을 경우
포인터 타입을 인자로 가진 함수를 호출한다.
그렇기에 이제는 NULL,0을 사용하지 말고 포인터 변수의 아무것도 가르키지 않을 때의 초기화는 무조건 nullptr사용.
-> 더이상 기존의 NULL을 사용할 이유가 없다.
'게임프로그래밍 > C++' 카테고리의 다른 글
[C++11] using, enum class (1) | 2022.01.22 |
---|---|
[C++11] default,delete,final,override 키워드 (0) | 2022.01.22 |
[C++] C++의 4가지 캐스팅(형변환) (0) | 2022.01.17 |
[C++] 템플릿 (0) | 2022.01.16 |
[C++] 연산자 오버로딩 (0) | 2022.01.15 |