스마트 포인터
C++은 타 언어와 다르게 가비지 컬렉터가 없기 때문에 동적으로 할당한 메모리에 대해서는 메모리 누수가 발생하기 쉽습니다.
따라서, 동적으로 할당된 메모리를 관리를 위해 C++14부터 스마트 포인터가 생기게 되었습니다.
스마트 포인터에 동적으로 할당된 메모리 경우 특정 기준에 의해 사용이 완료되었다고 판단이 될 때 메모리를 해제해줍니다
스마트 포인터 | 설명 | 비고 |
unique_ptr | 한 객체, 하나의 함수에서 만 사용할 때 사용. | |
shared_ptr | 특정 포인터의 복사본을 여러 객체나 코드에서 가지고 있을 때(공동 소유권), 이 포인터는 메모리에 대한 참조되는 숫자가 0이 될때 메모리를 해제. | 공유될때 마다 레퍼런싱 카운팅을 수행. |
weak_ptr | 지정된 메모리에 대한 소유권을 가지지 않는 포인터. 이 때문에 메모리 해제에 대한 영향을 주지 않음. |
1. unique_ptr 사용법
1) 생성 방법
작성 | 예제 |
std::unique_ptr<자료형> 변수명(new 자료형); | std::unique_ptr<int> intPtr(new int(20)); // 메모리에 20을 가진 intPtr형 포인터 std::unique_ptr<std::string> strPtr(new std::string("abcd")); // "abcd"std::string 객체 포인터 |
auto 변수명 = std::make_unique<자료형>(입력 값) | auto intPtr= std::make_unique<int>(20); // 할당된 int형 포인터 메모리에 20을 입력 auto strPtr= std::make_unique<std::string>("abcd");// "abcd"std::string 객체 포인터 |
가독성을 위해 std::make_unique()사용을 권장
2) 참조 방법
일반 포인터와 동일하게 아래와 같이 참조
(*test_ptr).run(); 또는 test_ptr->run();으로 표현 됩니다.
3) 소유권 이동
std::move()를 통해 소유권을 이동 시킨다.
#include<memory>
#include<iostream>
class Test
{
public:
Test(std::unique_ptr<int> data): mData(std::move(data)){} //소유권 이전
private:
std::unique_ptr<int> mData;
};
void main()
{
auto originPtr = std::make_unique<int>(20);
Test t(std::move(originPtr)); // originPtr -> Test생성자 파라미터로 소유권 이전
}
2. shared_ptr 사용법
1) 생성 방법
auto 변수명 = std::make_shared<자료형>(입력 값); | auto int_ptr = std::make_shared(20); auto strPtr= std::make_shared("abcd");// |
2) 캐스팅
const_pointer_cast()
dynamic_pointer_cast()
static_pointer_cast()
reinterpret_pointer_cast() // C++17이후부터
3) 참조 방법
일반 포인터와 동일하게 아래와 같이 참조
(*test_ptr).run(); 또는 test_ptr->run();으로 표현 됩니다.
4) 앨리어싱(aliasing)
소유 포인터를 다른 shared_ptr과 공유하면서 다른 객체를 가르킬수 있다. 아래코드는 shared_ptr이 객체를 가르키는 동시에 객체 멤버를 동시에 가르킬수 있습니다. 이때 메모리 해제는 t와 aliasing 모두 스코프에서 벗어날때 진행됩니다.
#include<memory>
#include<iostream>
Class Test
{
private:
Test(int data): mData(data){}
int mData;
};
void main()
{
auto t = std::make_shared<Test>(20);
auto asliaing = std::shared_ptr<int>(t, &t->mData);
}
3. weak_ptr 사용법
1) 생성 방법
weak포인터의 경우 shared_ptr을 가져다가 쓰기 때문에 아래와 같이 사용됩니다.
auto shared변수명 = std::make_shared<자료형>(입력 값); std::weak_ptr<자료형> weak변수명(shared변수명) |
auto int_ptr = std::make_shared(20); std::weak_ptr<int> weakInt(int_ptr); |
2) 사용법
weak_ptr에 저장된 포인터에 접근하려면 shared_ptr로 변환해야한다.
이때 방법은 weak_ptr의 lock()메소드를 사용하는 방법과 weak포인터를 생성할때 shared_ptr을 인수로 전달하는 방법이 있다.
auto int_ptr = std::make_shared(20);
std::weak_ptr<int> weakInt(int_ptr); // shared_ptr을 인수로 넘겨줌
weakInt.lock();//int_ptr의 shared_ptr을 리턴
casting
캐스팅 연산자를 사용하여 변수의 타입을 다른 타입으로 변환할 수 있습니다.
다음은 C++에서 사용할 수 있는 캐스팅 연산자의 종류입니다:
- 정적 캐스팅(static_cast): 컴파일 시간에 타입을 변환하는데 사용됩니다. 주로 상위 클래스에서 하위 클래스로의 포인터 혹은 참조자 변환, 산술 타입 간의 변환 등에 사용됩니다.
- static_cast는 주로 업 캐스팅(upcasting), 즉 하위 클래스의 객체를 상위 클래스 타입으로 캐스팅하는 데 사용됨 - 동적 캐스팅(dynamic_cast): 실행 시간에 타입을 변환하는데 사용됩니다. 주로 상속 관계에서의 업캐스팅과 다운캐스팅에 사용됩니다. 다운캐스팅 시, 안전한 타입 변환이 아닐 경우 nullptr을 반환합니다.
- 주로 다운 캐스팅(downcasting) 즉, 상위 클래스(Parent)의 객체를 하위 클래스(Child) 타입으로 캐스팅하는데 사용
- 다운캐스팅을 하기 위해서는 특정 조건이여야만 가능하다.
: 다형성을 지원하는 클래스 [ 상위 클래스에 최소 하나 이상의 가상 함수가 있어야 함 ]
: 변환하려는 타입이 실제로 해당 타입의 객체를 가리키고 있어야 함. [ ex) 업캐스팅된 부모의 인스턴스 ] - 상수 캐스팅(const_cast): 상수성(const)을 제거하는데 사용됩니다. 상수성이 있는 변수를 상수성이 없는 변수로 변환할 수 있습니다.
- 조건 : 포인터 및 참조형에서만 사용 가능하며, 상수 속성(const) 및 volatile 제거할 때 사용가능 - 임시 캐스팅(reinterpret_cast): 포인터 혹은 참조자의 타입을 다른 포인터 혹은 참조자의 타입으로 변환하는데 사용됩니다. 이 연산자는 매우 위험하며, 사용에 주의해야 합니다.
- 조건 :
용어 설명 :
- 자식 클래스의 참조 또는 포인터를 부모 클래스의 참조, 포인터로 변환하는 것을 Upcasting이라 합니다.
- 반대로 부모 클래스의 참조, 포인터를 명시적인 형변환을 통해 자식 클래스로 변환하는 것을 Downcasting이라 합니다.
예제
#include <iostream>
#include <typeinfo>
#include <string>
class Parent
{
public:
void say()
{
std::cout << "parent say" << std::endl;
}
virtual void down_cast_test()
{
std::cout << "parent cast" << std::endl;
}
};
class Child : public Parent
{
public:
void say()
{
std::cout << "Child say" << std::endl;
}
virtual void down_cast_test()
{
std::cout << "Child cast" << std::endl;
}
};
int main()
{
Parent p_instance;
Parent* ptr_p_instance;
Child c_instance;
Child* ptr_c_instance;
float b; // 실수
int a; // 정수
p_instance.say(); // 출력값 : parent say
c_instance.say(); // 출력값 : Child say
std::cout << "------------------------- static_cast --------------------------------" << std::endl;
// 실수를 정수로
b = 10.111;
a = static_cast<int>(b);
std::cout << typeid(a).name() << std::endl; // 출력값 : i
// 정수를 실수로 변환
b = static_cast<float>(a);
std::cout << typeid(b).name() << std::endl; // 출력값 : f
// 자식 클래스 포인터가 자식 클래스를 가리키는 것도 가능
c_instance = static_cast<Child>(c_instance);
c_instance.say(); // 출력값 : Child say
// 부모 클래스 포인터가 자식 클래스를 가리키는 것도 가능
ptr_p_instance = static_cast<Child*>(&c_instance);
ptr_p_instance->say(); // 출력값 : parent say
// 자식 클래스 포인터가 부모 클래스를 가리키는 것도 가능
ptr_c_instance = static_cast<Child*>(&p_instance);
ptr_c_instance->say(); // 출력값 : Child say
std::cout << "------------------------- dynamic_cast --------------------------------" << std::endl;
Parent* p_ptr_c_instance = &c_instance; // 포인터를 통해 업캐스팅 [ 자식 -> 부모 ]
Parent& ref_p_instance = c_instance; // 참조자를 통해 업캐스팅 [ 자식 -> 부모 ]
// Child* aa = &p_instance; // 컴파일 에러 // 업캐스팅 [ 부모 -> 자식 불가능]
// 자료형은 Parent* 또는 Parent& 임에도 Child 객체를 넣었고 + 동적바인딩된 상태이므로 Child의 down_cast_test()가 호출됨
p_ptr_c_instance->down_cast_test(); // 출력값 : Child cast
ref_p_instance.down_cast_test(); // 출력값 : Child cast
// // 컵파일 에러 ! // 이유 : dynamic_cast[다운캐스팅]은 타겟[Child,p_instance] 가 둘다 포인터나 참조자여야 함.
// p_instance = dynamic_cast<Child>(p_instance);
// p_instance.down_cast_test();
// 부모의 주소값을 자식으 주소값으로 다운 캐스팅
ptr_c_instance = dynamic_cast<Child*>(p_ptr_c_instance);
ptr_c_instance->down_cast_test(); // 출력값 : Child cast
// 부모의 주소값을 자식으 주소값으로 다운 캐스팅
ref_p_instance = dynamic_cast<Child&>(ref_p_instance);
ref_p_instance.down_cast_test(); // 출력값 : Child cast
std::cout << "------------------------- const_cast --------------------------------" << std::endl;
// 1번, pointer 변수에 Const 지정자를 제거하는 예제
const int *const_int_ptr = new int(1);
int *int_ptr_1;
std::cout << *const_int_ptr << std::endl; // 출력값 : 1
int_ptr_1 = const_cast<int*>(const_int_ptr); // const_int_ptr의 const 지정자를 잠시 제거
*int_ptr_1 = 2; // const_int_ptr 의 값을 변경
std::cout << *const_int_ptr << std::endl; // 출력값 : 2
std::cout << "------------------------- reinterpret_cast --------------------------------" << std::endl;
typedef typename std::string string;
// 일반 포인터 캐스팅시
int *int_ptr_2 = new int(10);
char *char_ptr_1;
std::cout << *int_ptr_2 << std::endl; // 출력값 : 10
char_ptr_1 = reinterpret_cast<char*>(int_ptr_2);
*char_ptr_1 = 'A'; // int_ptr_2 의 값을 변경
std::cout << *int_ptr_2 << std::endl; // 출력값 : 65 [ 'A' 의 아스키값이 65 ]
// const 인 포인터 캐스팅시
const int *int_ptr_3 = new int(30);
char *char_ptr_2;
std::cout << *int_ptr_3 << std::endl; // 출력값 : 30
char_ptr_2 = reinterpret_cast<char*>(const_cast<int*>(int_ptr_3));
*char_ptr_2 = 40; // int_ptr_3 의 값을 변경
std::cout << *int_ptr_3 << std::endl; // 출력값 : 40
std::cout << "------------------------- END --------------------------------" << std::endl;
return 0;
}
출력 :
'언어 정리 > C++_개념_lib' 카테고리의 다른 글
this, function return 자료형 (0) | 2024.01.16 |
---|---|
char , 배열과 포인터 차이 (1) | 2024.01.04 |
typedef (0) | 2024.01.03 |
#if, 초기화리스트, const, namespace (0) | 2024.01.02 |
template 설명 [ fold_expression ] (0) | 2023.12.27 |
댓글