🔹 std::unique_ptr란?
std::unique_ptr는 C++의 스마트 포인터 중 하나로, 단 하나의 포인터만 객체를 소유할 수 있도록 설계된 스마트 포인터입니다.
즉, 같은 객체를 두 개 이상의 unique_ptr가 공유할 수 없으며, 소유권이 한 곳에만 존재합니다.
소유하고 있는 unique_ptr가 범위를 벗어나거나 reset()되면 자동으로 객체가 삭제되어 메모리 누수를 방지할 수 있습니다.
✅ std::unique_ptr의 특징
- 단독 소유(Exclusive Ownership)
- 하나의 unique_ptr만 특정 객체를 소유 가능.
- 다른 unique_ptr에 복사할 수 없음(copy constructor와 copy assignment가 삭제됨).
- 자동 메모리 관리
- unique_ptr가 스코프를 벗어나면 자동으로 객체를 삭제 (delete 호출 필요 없음).
- 명시적으로 reset()을 호출하면 객체를 해제하고 새로운 객체를 할당 가능.
- 소유권 이동 가능
- std::move()를 사용하여 소유권을 다른 unique_ptr로 이전 가능.
- 성능이 뛰어남
- 참조 카운팅이 없는 구조라서 shared_ptr보다 오버헤드가 적음.
- 동적 할당된 리소스를 효율적으로 관리할 수 있음.
📌 std::unique_ptr 사용법
1️⃣ 기본적인 사용법
#include <iostream>
#include <memory>
class Test {
public:
Test() { std::cout << "Test 생성\n"; }
~Test() { std::cout << "Test 소멸\n"; }
void show() { std::cout << "Test 클래스\n"; }
};
int main() {
std::unique_ptr<Test> uptr = std::make_unique<Test>(); // 객체 생성 및 관리
uptr->show(); // 포인터처럼 객체에 접근 가능
return 0; // uptr이 스코프를 벗어나며 자동으로 Test 객체가 소멸됨
}
출력 예시
Test 생성
Test 클래스
Test 소멸
✅ std::make_unique<Test>()를 사용하면 객체를 생성하면서 unique_ptr를 안전하게 초기화할 수 있음.
2️⃣ 소유권 이전 (std::move)
unique_ptr는 복사할 수 없지만, 소유권을 이전(move)할 수는 있음.
std::unique_ptr<Test> uptr1 = std::make_unique<Test>();
std::unique_ptr<Test> uptr2 = uptr1; // ❌ 오류 (복사 불가능)
std::unique_ptr<Test> uptr3 = std::move(uptr1); // ✅ 이동 가능
🔹 std::move()를 이용해 소유권을 이동하면 uptr1은 더 이상 객체를 소유하지 않음.
#include <iostream>
#include <memory>
class Test {
public:
Test() { std::cout << "Test 생성\n"; }
~Test() { std::cout << "Test 소멸\n"; }
};
int main() {
std::unique_ptr<Test> uptr1 = std::make_unique<Test>(); // 객체 생성
std::unique_ptr<Test> uptr2 = std::move(uptr1); // 소유권 이동
if (!uptr1) {
std::cout << "uptr1은 이제 nullptr입니다.\n";
}
return 0; // uptr2가 스코프를 벗어나며 객체 소멸
}
출력 예시
Test 생성
uptr1은 이제 nullptr입니다.
Test 소멸
✅ uptr1은 더 이상 객체를 소유하지 않고, uptr2가 객체를 소유함.
3️⃣ reset()을 이용한 해제
- reset()을 호출하면 unique_ptr가 관리하는 객체가 즉시 해제됨.
- 새 객체를 할당할 수도 있음.
#include <iostream>
#include <memory>
class Test {
public:
Test() { std::cout << "Test 생성\n"; }
~Test() { std::cout << "Test 소멸\n"; }
};
int main() {
std::unique_ptr<Test> uptr = std::make_unique<Test>();
uptr.reset(); // 객체 즉시 해제
std::cout << "reset() 호출 후\n";
return 0;
}
출력 예시
Test 생성
Test 소멸
reset() 호출 후
✅ reset()을 호출하면 unique_ptr가 즉시 객체를 해제함.
4️⃣ 커스텀 삭제자 사용하기
기본적으로 unique_ptr는 delete를 사용하여 객체를 삭제하지만, 커스텀 삭제자를 지정할 수도 있음.
#include <iostream>
#include <memory>
class Test {
public:
Test() { std::cout << "Test 생성\n"; }
~Test() { std::cout << "Test 소멸\n"; }
};
void customDeleter(Test* ptr) {
std::cout << "사용자 정의 삭제자 호출\n";
delete ptr;
}
int main() {
std::unique_ptr<Test, decltype(&customDeleter)> uptr(new Test(), customDeleter);
return 0;
}
출력 예시
Test 생성
사용자 정의 삭제자 호출
Test 소멸
✅ customDeleter()가 호출된 후 객체가 삭제됨.
📌 std::unique_ptr vs std::shared_ptr
특징 | std::unique_ptr | std::shared_ptr |
소유권 | 단독 소유 | 여러 shared_ptr가 공유 |
복사 | ❌ 불가능 | ✅ 가능 (참조 카운트 증가) |
이동(std::move) | ✅ 가능 | ✅ 가능 |
성능 | shared_ptr보다 가벼움 (참조 카운트 없음) | 참조 카운트 증가로 인해 약간의 오버헤드 |
메모리 관리 | 자동 해제 | 참조 카운트가 0이 될 때 해제 |
순환 참조 문제 | 없음 | 발생 가능 (해결하려면 weak_ptr 사용) |
🎯 결론
- std::unique_ptr는 단 하나의 포인터만 객체를 소유할 수 있는 단독 소유 스마트 포인터.
- shared_ptr보다 가볍고 빠르며, 메모리 누수를 방지할 수 있음.
- 복사가 불가능하지만, **소유권 이동(std::move)**을 통해 다른 unique_ptr로 넘길 수 있음.
- reset()으로 객체를 명시적으로 해제할 수도 있음.
- 동적 할당된 객체를 관리할 때 std::make_unique<T>()를 사용하는 것이 권장됨.
🚀 일반적으로 std::unique_ptr를 기본적으로 사용하고, 객체를 공유해야 할 경우 std::shared_ptr를 사용하면 됨!
🔹 std:: shared_ptr 란?
std::shared_ptr는 C++의 스마트 포인터 중 하나로, 여러 개의 shared_ptr 인스턴스가 같은 객체를 공유할 수 있도록 해주는 스마트 포인터입니다.
✅ std::unique_ptr의 특징
- 참조 카운팅(Reference Counting)
- shared_ptr는 내부적으로 참조 카운트(reference count)를 유지합니다.
- 하나의 shared_ptr이 새 객체를 소유하면 참조 카운트가 1이 됩니다.
- shared_ptr가 복사될 때마다 참조 카운트가 증가합니다.
- shared_ptr가 소멸되거나 reset()이 호출될 때 참조 카운트가 감소합니다.
- 마지막 shared_ptr이 소멸되면 참조 카운트가 0이 되어 객체가 자동으로 삭제됩니다.
- 자동 메모리 관리
- shared_ptr가 더 이상 필요하지 않으면 자동으로 객체를 해제하므로, 메모리 관리가 쉬워집니다.
- 다중 소유 가능
- 여러 개의 shared_ptr가 같은 객체를 공유할 수 있습니다.
📌 std::unique_ptr 사용법
1️⃣ 기본적인 사용법
#include <iostream>
#include <memory>
class Test {
public:
Test() { std::cout << "Test 생성\n"; }
~Test() { std::cout << "Test 소멸\n"; }
void show() { std::cout << "Test 클래스\n"; }
};
int main() {
std::shared_ptr<Test> p1 = std::make_shared<Test>(); // 객체 생성 및 관리 시작
{
std::shared_ptr<Test> p2 = p1; // 참조 카운트 증가
p2->show();
std::cout << "참조 카운트: " << p1.use_count() << "\n"; // 2
} // p2가 소멸되며 참조 카운트 감소
std::cout << "참조 카운트: " << p1.use_count() << "\n"; // 1
} // p1이 소멸되며 객체 삭제
출력 예시
Test 생성
Test 클래스
참조 카운트: 2
참조 카운트: 1
Test 소멸
2️⃣ std::make_shared 사용
- std::make_shared<T>(...)를 사용하면 shared_ptr를 생성할 때 동적 할당을 한 번만 수행하므로 성능이 더 좋습니다.
std::shared_ptr<Test> p = std::make_shared<Test>();
3️⃣ reset()과 use_count()
- reset()을 호출하면 현재 객체와의 연결이 끊어지고, 참조 카운트가 감소합니다.
- use_count()는 현재 객체를 참조하는 shared_ptr의 개수를 반환합니다.
p1.reset(); // 객체 소멸
std::cout << p1.use_count() << "\n"; // 0
📌주의할 점
- 순환 참조 문제
- shared_ptr를 서로가 참조하는 경우, 참조 카운트가 0이 되지 않아 메모리 누수가 발생할 수 있습니다.
- 이를 방지하려면 std::weak_ptr를 함께 사용해야 합니다.
#include <iostream>
#include <memory>
class B; // 전방 선언
class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A 소멸\n"; }
};
class B {
public:
std::shared_ptr<A> a_ptr; // 순환 참조 발생
~B() { std::cout << "B 소멸\n"; }
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a; // 순환 참조 발생
return 0; // 메모리 누수 발생 (A와 B가 소멸되지 않음)
}
해결 방법
- std::shared_ptr 대신 std::weak_ptr 사용
class B;
class A {
public:
std::weak_ptr<B> b_ptr; // weak_ptr 사용
~A() { std::cout << "A 소멸\n"; }
};
class B {
public:
std::shared_ptr<A> a_ptr;
~B() { std::cout << "B 소멸\n"; }
};
🎯 결론
- shared_ptr는 참조 카운트를 사용하여 자동으로 객체를 관리하는 스마트 포인터.
- std::make_shared를 사용하면 성능이 향상됨.
- 순환 참조를 피하기 위해 std::weak_ptr를 함께 사용해야 함.
🔹 std::weak_ptr 란?
std::weak_ptr는 std::shared_ptr와 함께 사용되는 스마트 포인터로, 소유권을 가지지 않는 참조를 제공하여 순환 참조(circular reference) 문제를 방지하는 역할을 합니다.
✅ std::weak_ptr의 특징
- 객체의 소유권을 가지지 않음
- shared_ptr처럼 참조 카운트(use_count())를 증가시키지 않음.
- shared_ptr가 관리하는 객체가 소멸되면 weak_ptr는 자동으로 무효(invalid)가 됨.
- 순환 참조(Circular Reference) 문제 해결
- shared_ptr끼리 서로를 참조하면 참조 카운트가 0이 되지 않아 메모리 누수가 발생할 수 있음.
- weak_ptr을 사용하면 참조 카운트가 증가하지 않아 객체가 정상적으로 해제됨.
- lock()을 사용하여 shared_ptr로 변환 가능
- lock()을 호출하면 유효한 객체가 있을 경우 shared_ptr을 반환하여 안전하게 객체를 사용 가능.
- 이미 소멸된 객체라면 nullptr을 반환.
📌 사용법
1️⃣ 기본적인 weak_ptr 사용
#include <iostream>
#include <memory>
class Test {
public:
Test() { std::cout << "Test 생성\n"; }
~Test() { std::cout << "Test 소멸\n"; }
void show() { std::cout << "Test 클래스\n"; }
};
int main() {
std::shared_ptr<Test> sp = std::make_shared<Test>(); // 객체 생성
std::weak_ptr<Test> wp = sp; // weak_ptr에 shared_ptr 할당 (소유권 X)
std::cout << "use_count: " << sp.use_count() << "\n"; // 1
if (auto shared = wp.lock()) { // weak_ptr을 shared_ptr로 변환
shared->show();
std::cout << "use_count (lock 후): " << sp.use_count() << "\n"; // 2
}
sp.reset(); // shared_ptr 해제 (객체 소멸)
if (wp.expired()) {
std::cout << "객체가 소멸됨\n";
}
return 0;
}
출력 예시
Test 생성
use_count: 1
Test 클래스
use_count (lock 후): 2
Test 소멸
객체가 소멸됨
2️⃣ weak_ptr을 활용한 순환 참조 해결
❌ 순환 참조 문제 (메모리 누수 발생)
#include <iostream>
#include <memory>
class B; // 전방 선언
class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A 소멸\n"; }
};
class B {
public:
std::shared_ptr<A> a_ptr;
~B() { std::cout << "B 소멸\n"; }
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a; // ❌ 순환 참조 발생 (메모리 누수)
return 0; // A와 B가 소멸되지 않음 (use_count가 0이 되지 않음)
}
출력 없음 (메모리 누수 발생)
✅ weak_ptr을 사용하여 순환 참조 해결
#include <iostream>
#include <memory>
class B; // 전방 선언
class A {
public:
std::weak_ptr<B> b_ptr; // weak_ptr 사용 (소유권 X)
~A() { std::cout << "A 소멸\n"; }
};
class B {
public:
std::shared_ptr<A> a_ptr;
~B() { std::cout << "B 소멸\n"; }
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b; // weak_ptr 사용
b->a_ptr = a; // shared_ptr 유지
return 0; // A와 B가 정상적으로 소멸됨
}
출력
B 소멸
A 소멸
✅ weak_ptr을 사용하여 B가 A를 소유하지 않게 되었기 때문에, B가 먼저 소멸되고 A가 정상적으로 소멸됨.
📌 주요 함수
함수 설명
expired() | 관리하는 객체가 소멸되었는지 확인 (true: 소멸됨) |
lock() | shared_ptr을 생성하여 객체 사용 (nullptr 반환 가능) |
reset() | 현재 weak_ptr을 해제 |
use_count() | shared_ptr의 참조 카운트 반환 |
🎯 결론
- std::weak_ptr은 std::shared_ptr과 함께 사용되며 소유권을 가지지 않음.
- 순환 참조 문제를 해결하는 데 사용됨.
- expired()와 lock()을 사용하여 객체가 유효한지 확인하고 안전하게 접근 가능.
- std::make_shared와 함께 사용하면 더 효율적.
'프로그래밍 > C,C++' 카테고리의 다른 글
[Modern C++] std::thread (0) | 2025.03.11 |
---|---|
[Modern C++] 우측값 레퍼런스 (0) | 2025.02.24 |
[Mordern C++] golang-style defer 만들기 (1) | 2024.11.29 |
함수(function) (0) | 2015.03.24 |
상속의 기능 (0) | 2015.02.14 |