본문 바로가기
언어 정리/C++_개념_lib

스마트 포인터, casting

by 알 수 없는 사용자 2024. 1. 3.

 

스마트 포인터

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++에서 사용할 수 있는 캐스팅 연산자의 종류입니다:

 

  1. 정적 캐스팅(static_cast): 컴파일 시간에 타입을 변환하는데 사용됩니다. 주로 상위 클래스에서 하위 클래스로의 포인터 혹은 참조자 변환, 산술 타입 간의 변환 등에 사용됩니다.
      - static_cast는 주로 업 캐스팅(upcasting), 즉 하위 클래스의 객체를 상위 클래스 타입으로 캐스팅하는 데 사용됨

  2. 동적 캐스팅(dynamic_cast): 실행 시간에 타입을 변환하는데 사용됩니다. 주로 상속 관계에서의 업캐스팅과 다운캐스팅에 사용됩니다. 다운캐스팅 시, 안전한 타입 변환이 아닐 경우 nullptr을 반환합니다.
      - 주로 다운 캐스팅(downcasting) 즉, 상위 클래스(Parent)의 객체를 하위 클래스(Child) 타입으로 캐스팅하는데 사용
      - 다운캐스팅을 하기 위해서는 특정 조건이여야만 가능하다.
        : 다형성을 지원하는 클래스 [ 상위 클래스에 최소 하나 이상의 가상 함수가 있어야 함 ]
        : 변환하려는 타입이 실제로 해당 타입의 객체를 가리키고 있어야 함. [ ex) 업캐스팅된 부모의 인스턴스 ]

  3. 상수 캐스팅(const_cast): 상수성(const)을 제거하는데 사용됩니다. 상수성이 있는 변수를 상수성이 없는 변수로 변환할 수 있습니다.
      - 조건 : 포인터 및 참조형에서만 사용 가능하며, 상수 속성(const) 및 volatile 제거할 때 사용가능

  4. 임시 캐스팅(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

댓글