초기화하지 않은 포인터에는 무작위 값을 할당하기 때문에, 초기화하지 않은 포인터를 사용하면 오류가 발생할 수 있다. 따라서 포인터가 무언가를 가리키고 있지 않다는 것을 명시적으로 알려주려면 다음과 같이 설정해야 한다.
int* ip = nullptr;
int* ip2{};
위와 같은 방법은 c++ 11이상에서 좋은 방법이다. 반면,
int* ip3 = 0;
int* ip4 = NULL;
은 c++ 11 이상에서는 좋지 않다. 주소값 0은 절대 사용되지 않기 때문에 포인터가 비어있다는 것을 잘 알려주지만 int 0으로 함수 오버로딩에서 모호함을 유발할 수 있다. 또한 NULL
은 단지 전처리 과정에서 0
으로 정의되어있기 때문에 같은 이유로 좋지 않다.
c++ 11에 새로운 스마트 포인터 3가지를 도입했다. c++ 03에 있던 auto_ptr
은 더이상 사용하면 안 된다. 만약 c++ 11 이상 사용할 수 없다면 Boost에 있는 스마트 포인터로 대체해야 한다.
이 포인터는 참조한 데이터의 고유 소유권을 나타낸다.
1 #include <memory>
2 #include <iostream>
3
4 int main()
5 {
6 std::unique_ptr<double> dp{new double};
7
8 *dp = 7;
9
10 std::cout << *dp << std::endl; //expected 7
11
12 //double d;
13 //unique_ptr<doubledd{d}; //expected error. Because d is not allocated dinamically.
14
15 std::unique_ptr<doubledp2{move(dp)}; //delete dp and move the value of dp to dp2 uniquely.
16
17 if (dp == nullptr)
18 {
19 std::cout << "dp is nullptr." << std::endl;
20 }
21
22 std::cout << "dp2 : " << *dp2 << std::endl;
23
24 std::unique_ptr<doubledp3;
25 dp3 = move(dp2); //delete dp2 and move the value of dp2 to dp3 uniquely.
26
27 if (dp2 == nullptr)
28 {
29 std::cout << "dp2 is nullptr." << std::endl;
30 }
31
32 std::cout << "dp3 : " << *dp3 << std::endl;
33
34 return 0;
35 }
7
dp is nullptr.
dp2 : 7
dp2 is nullptr.
dp3 : 7
10: 결과로 dp를 출력했을 때 7이 콘솔 창에 출력된다. 또한 main 함수가 종료될 때, 자동으로 변수 dp
에 할당된 메모리에 가서 할당을 해제할 것이다.
15: dp
를 dp2
로 move하는 과정에서 dp
는 nullptr
이 됨을 알 수 있다. dp
가 nullptr
가 되면서 고유 소유권이 유지된다.
1 #include <memory>
2 #include <iostream>
3
4 int main()
5 {
6 std::unique_ptr<double[]da{new double[3]};
7
8 for (int i=0; i<3; ++i) //available for-loop
9 {
10 da[i] = i;
11 }
12
13 for (int i=0; i<3; ++i)
14 {
15 std::cout << "i : " << i << ", da[i] : " << da[i] << std::endl;
16 }
17
18 #if 0
19 for (int j=0; j<3; ++j) //NOT available for-loop due to * operator
20 {
21 *(da+j) = j;
22 }
23 #endif
24
25 return 0;
26 }
i : 0, da[i] : 0
i : 1, da[i] : 1
i : 2, da[i] : 2
shared_ptr
은 여러 파티에서 공통으로 메모리를 관리한다. 또한 shared_ptr
가 더 이상 데이터를 참조하지 않는 즉시 메모리를 자동으로 해제한다. 이는 복잡한 데이터 구조의 경우 매우 편리하다. 모든 스레드가 스레드에 대한 접근이 끝나면 메모리를 자동으로 해제하기 때문이다.
또한 공유 소유권을 갖기 때문에 원하는 만큼 자주 복사할 수 있다.
1 #include <memory>
2 #include <iostream>
3
4 int main()
5 {
6 std::shared_ptr<doubledp4{new double}; //NOT recommanded way due to memory struture of shared_ptr.
7 *dp4 = 6;
8
9 std::shared_ptr<doubledp5 = dp4;
10
11 std::cout << "dp4 : " << *dp4 << ", dp5 : " << *dp5 << std::endl;
12
13 std::shared_ptr<doubledp6 = dp4;
14 std::cout << "dp6 : " << *dp6 << std::endl;
15
16 std::cout << "dp4.use_count() : " << dp4.use_count() << ", dp5.use_count() : " << dp5.use_count() << ", dp6.use_count() : " << dp6.use_ count() << std::endl;
17
18 std::shared_ptr<doubledp7 = std::make_shared<double>(); //better way.
19 *dp7 = 5;
20 std::cout << "dp7 : " << *dp7 << std::endl;
21
22 auto dp8 = std::make_shared<double>(); //best way.
23 *dp8 = 4;
24 std::cout << "dp8 : " << *dp8 << std::endl;
25
26
27 return 0;
28 }
6: new
operator를 이용하면 dp4는 counter, manager 등 data 이외의 값들이 저장된 블록과 data가 저장된 블록을 임의로(대부분 다른 영역으로) 할당한다. 따라서 메모리 관리에 문제를 일으킬 수도 있다. 따라서 18 혹은 22번째 줄과 같이 std::make_shared
를 이용해서 할당해야 한다.
16: shared_ptr
의 멤버 함수인 use_count()
는 shared_ptr
이 가리키는 메모리가 몇 번의 참조를 받는지를 return하는 함수이다.
22: make_shared
는 명시적으로 shared_ptr
을 return하기 때문에 auto
를 사용하는 것이 휴먼 에러를 줄일 수 있는 방법이다.
레퍼런스를 별칭으로 생각할 수 있다. 즉, 기존에 있는 개체 또는 하위 개체에 새로운 이름을 도입하는 것이다. 레퍼런스를 정의할 때 마다 포인터와는 달리 어떤 변수를 참조할 것인지를 직접 선언해야 한다. 나중에 다른 변수를 참조할 수는 없다.
1 #include <iostream>
2
3 int main()
4 {
5 int i = 5;
6 int& j = i;
7
8 std::cout << "i : " << i << ", j : " << j << std::endl;
9
10 i = 6;
11 std::cout << "i : " << i << ", j : " << j << std::endl;
12
13 j = 7;
14 std::cout << "i : " << i << ", j : " << j << std::endl;
15
16 return 0;
17 }
i : 5, j : 5
i : 6, j : 6
i : 7, j : 7
6: j
가 i
를 참조하기 때문에 i
와 j
가 항상 같은 값을 갖고 있다. 쉽게 말해 j
는 i
의 별칭이다.
레퍼런스의 특징은 아래와 같다.
반면 포인터의 특징은 아래와 같다.
double& square_ref(double d) {
double s = d * d;
return s;
}
위 코드에서는 square_ref
함수가 더이상 존재하지 않는 함수 내 지역 변수 s
를 참조해서 리턴한다. square_ref
함수를 호출해서 사용하는 코드에서 s
의 메모리가 남아있는지는 불확실하기 때문에 위 코드는 지양하는 것이 좋다.
따라서 동적으로 할당할 데이터나 함수를 호출하기 전의 데이터 또는 static 데이터의 포인터나 레퍼런스를 리턴하는 것이 좋다.