일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- 스마트포인터
- 람다
- 자료구조
- 정렬알고리즘
- unorder_map
- UE4 커스텀로그
- C++
- 약참조
- map
- 애셋로드
- stl
- BFS
- 정렬
- UELOG
- 데이터애셋
- 크리티컬섹션
- 언리얼가비지컬렉터
- 언리얼엔진구조체
- 람다사용정렬
- 프로그래머스
- 강참조
- C++최적화
- enumasByue
- UML관련
- 델리게이트
- moreeffectiveC++
- dataasset
- 알고리즘
- 선택정렬
- UE_LOG
- Today
- Total
기억을 위한 기록들
[CPP]스마트(Smart) 포인터에 관하여(1) - unique_ptr 본문
스마트 포인터는 3가지가 있다.
- unique_ptr (유니크)
- shared_ptr (쉐어드)
- weak_ptr (위크)
우선 일반 포인터를 보면
//...
int main()
{
Vector* myVector = new Vector(10.f, 30.f);
//...
delete myVector;
return 0;
}
이런식으로 delete를 해줘야 한다. 그러나
//...
int main()
{
Vector* myVector = new Vector(10.f, 30.f);
if(true)
{
return 0; //early return
}
delete myVector;
return 0;
}
중간에 끝나버리거나(try catch와 같은), delete을 잊으면 메모리 누수(memory leak)가 발생한다. "나중에 써야지.." 하
는것보다 바로 쓰는게 낫다.
뭐가 문제일까?
- 포인터가 필요하지 않을 때 메모리를 해제 해야함.
스마트 포인터를 쓰면 delete를 직접 호출할 필요가 없다. 그리고 가비지 컬렉션보다 빠르다.
3가지를 살펴보자
1. unique_ptr (유니크 포인터) (C++11)
유니크하다. 하나 밖에 없다 라는 일반적인 뜻으로 소유자는 나 말고 못쓰게 만드는 포인터
유니크 포인터를 만드는 것을 보면
#include <memory>
#include "Vector.h"
int main()
{
std::unique_ptr<Vector> myVector(new Vector(10.f,30.f)); //* 부호가 없음
myVector->Print(); //그러나 포인터처럼 동작한다.
return 0; //delete는 없다.
}
위 예는 벡터형 유니크포인터라고 한다.
유니크 포인터의 특징은
- 포인터(원시 포인터라고 하자)를 단독으로 소유
- 원시(naked)포인터는 누구하고도 공유되지 않음.
- 따라서 복사나 대입불가
- 유니크포인터는 범위(scope)를 벗어날 때, 원시 포인터는 알아서 지워짐(delete).
유니포인트를 사용하기 좋은 경우 3가지
1. 클래스에서 생성자/소멸자
Class Player
{
private:
std::unique_ptr<Vector> mLocation;
};
Player::Player(std::string name)
: mLocation(new Vector()) //초기화만 해주면 된다.
{
}
//소멸자도 없고 delete도 없다.
한가지 단점으로는 해당 클래스는 다른클래스가 상속받을때 가상소멸자가 없어서 귀찮아진다. 근데 오버라이딩로 해결 가능.
2. 지역 변수 쓸 때
#include <memory>
#include "Vector.h"
int main()
{
std::unique_ptr<Vector> myVector(new Vector(10.f,30.f));
myVector->Print();
//...
}
예를 들어 스택에 1mb 잡혀 있는데 10mb 넣으려고 하면 문제가 생긴다. 이럴 때 어쩔수 없이 힙메모리에서 가져와야하는데 이럴때 유니크 메모리를 쓰면 해결 된다.
3. STL벡터에 포인터 저장하기
#include <memory>
#include "Player.h"
int main()
{
std::vector<std::unique_ptr<Player>> playerList;
playerList.push_back(std::unique_ptr<Player>(new Player("lala")));
playerList.push_back(std::unique_ptr<Player>(new Player("nono")));
playerList.clear(); //벡터 비울때 알아서 같이 해제됨
//for(int i=0;i<playerList.size();i++)
//{
// delete playerList[i]; //원시 포인터 였다면 이런식으로 해제 해줘야함.
//}
//...
}
문제점 : 원시 포인터와 공유
Vector* vectorPtr = new Vector(10.f, 30.f); //1.원시포인터 할당했다
std::unique_ptr<Vector> vector(vectorPtr); //2. 유니크 포인터에 복사해서 둘다 같은 주소값 가짐.
std::unique_ptr<Vector> anotherVector(vectorPtr); // 3.여기서 또 다른 유니크포인터가 위 2개와 주소값을 가짐.
anotherVector=null; //4. 그래서 지워줬더니 vectorPtr하고 vector둘다 주소가 가리키는 값이 지워짐.
그래서 C++14이후 해결책
#include <memory>
#include "Vector.h"
int main()
{
//힙 할당 불필요. 단지 std::make_unique 보여주기 위함.
std::unique_ptr<Vector> myVector = std::make_unique<Vector>(10.f,30.f);
myVector->Print();
return 0;
}
std::make_unique() 는 뭐 하길래?
- 주어진 매개변수와 자료형으로 new 키워드를 호출해줌
- 따라서 원시 포인터와 같음
- 둘 이상의 std::unique_ptr가 원시 포인터를 공유할수 없도록 막아준다.
위 문제점 예시가 컴파일 난다.
Vector* vectorPtr = new Vector(10.f, 30.f);
std::unique_ptr<Vector> vector1 =std::make_unique<Vector>(vectorPtr ); //컴파일 에러!
//std::unique_ptr<Vector> anotherVector(vectorPtr);
//anotherVector=null;
C+11에서는
std::unique_ptr<Vector> myVector(new Vector(10.f,30.f));
std::unique_ptr<Vector[]> myVectorArr(new Vector[]);
C+14에서는 make_unique 이다.
std::unique_ptr<Vector> myVector = std::make_unique<Vector>(10.f,30.f);
std::unique_ptr<Vector[]> myVectorArr = std::make_unique<Vector[]>(20);
유니크포인터 사용 함수
1. reset() : 포인터를 교체해준다. 재설정 될때, 소유하고 있던 원시포인터는 자동으로 소멸(null)
int main()
{
std::unique_ptr<Vector> myVector = std::make_unique<Vector>(10.f,30.f);
myVector.reset(new Vector(20.f,40.f)); //원래 있던걸 해제하고 새로 할당
myVector.reset(); //원래 갖고 있던거 해제하고 null
//...
}
vector.reset(); 하고 vector = null; 은 같다. 개인취향이긴 한데 후자가 좀더 보기 좋다
2. get() : 내부에서 갖고 있는 원시 포인터 반환한다.
int main()
{
std::unique_ptr<Vector> myVector = std::make_unique<Vector>(10.f,30.f);
Vector* ptr= myVector.get();
//...
}
3. release() : 소유권 이전(박탈하기)/ 잘쓰지 않음 (좋은 함수는 아니다.)
int main()
{
std::unique_ptr<Vector> myVector = std::make_unique<Vector>(10.f,30.f);
Vector* ptr= myVector.release();
//...
}
relase함수보다는 아래의 방법이 낫다.
소유권 이전하기 관련
move 함수 사용
int main()
{
std::unique_ptr<Vector> myVector = std::make_unique<Vector>(10.f,30.f);
std::unique_ptr<Vector> anotherVector(std::move(myVector));
//...
}
myVector가 가리키건 포인터를 anotherVector가 가리키고 myVector는 null이 된다. relase() 보다 안전함.
예외로는 const 유니크 포인터가 있다.
int main()
{
const std::unique_ptr<Vector> myVector = std::make_unique<Vector>(10.f,30.f);
//std::unique_ptr<Vector> anotherVector(std::move(myVector)); //컴파일 에러!
//...
}
- std::unique_ptr는 소유한 원시 포인터를 아무하고도 공유하지 않음.
- 즉, 주소 복사를 하지 않는다는 뜻
- 대신, 소유권을 다른 std::unique_ptr로 옮길 수 있음
- 예외 : const std::unique_ptr
std::move 관련
- 개체A의 모든 멤버를 포기하고 그 소유권을 B에게 주는 방법
- 메모리 할당과 해제가 일어나지 않음
- 간단하게 A의 모든 포인터를 B에게 대입하고, A는 nullptr
stl 벡터에 요소 추가할때도 move 사용
#include <memory>
#include "Player.h"
int main()
{
std::vector<std::unique_ptr<Player>> playerList;
std::unique_ptr<Player> coco= std::make_unique<Player>("Coco");
playerList.push_back(std::move(coco));
std::unique_ptr<Player> lala= std::make_unique<Player>("Lala");
playerList.push_back(std::move(lala));
}
정리
- 직접 메모리 관리하는 것 만큼 빠르다.
- RAII 원칙에 잘 들어 맞음
1. 자원 할당은 개체의 수명과 관련
2. 생성자에서 new 그리고 소멸자에서 delete
3. std::unique_ptr 멤버변수가 이걸 해줌
- 실수하기 어려움
'C & CPP > Smart Pointer' 카테고리의 다른 글
[CPP]스마트(Smart) 포인터에 관하여(3) - weak_ptr (0) | 2021.02.18 |
---|---|
[CPP]스마트(Smart) 포인터에 관하여(2) - shared_ptr (0) | 2021.02.18 |
가비지 컬렉션/참조 카운팅에 관하여 (0) | 2021.02.18 |