1. 순환 참조 문제 

 

(예제1)

#include <memory>
#include <iostream>

struct Node {
    std::shared_ptr<Node> next;
    int value;
    Node(int val): value(val) {
        std::cout << "Node " << value << " created." << std::endl;
    }
    ~Node() {
        std::cout << "Node " << value << " destroyed." << std::endl;
    }
};

int main() {
    std::shared_ptr<Node> head = std::make_shared<Node>(1);
    std::shared_ptr<Node> second = std::make_shared<Node>(2);
    std::shared_ptr<Node> third = std::make_shared<Node>(3);
    
    head->next = second;
    second->next = third;
    third->next = head; // 순환 참조
    
    return 0;
}

위 예제에서 Node 구조체는 단순히 value와 다음 노드를 가리키는 next 멤버 변수를 가지고 있습니다.

main 함수에서는 head, second, third 세 개의 노드를 생성하고 next 포인터로 연결합니다.

그런데 third 노드에서 head 노드를 가리키는 것을 볼 수 있습니다.

이로 인해 head가 참조하는 객체의 reference count가 2가 되어 메모리에서 해제되지 않고 유지됩니다.

프로그램을 실행해보면 다음과 같은 출력을 확인할 수 있습니다.

Node 1 created.
Node 2 created.
Node 3 created.
Node 1 destroyed.

 

(예제2)

#include <memory>
#include <iostream>

struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;
    int value;
    Node(int val): value(val) {
        std::cout << "Node " << value << " created." << std::endl;
    }
    ~Node() {
        std::cout << "Node " << value << " destroyed." << std::endl;
    }
};

int main() {
    std::shared_ptr<Node> head = std::make_shared<Node>(1);
    std::shared_ptr<Node> second = std::make_shared<Node>(2);
    std::shared_ptr<Node> third = std::make_shared<Node>(3);
    
    head->next = second;
    second->prev = head;
    second->next = third;
    third->prev = second;
    third->next = head; // 순환 참조
    
    return 0;
}

이 예제에서는 Node 구조체에 prev라는 멤버 변수를 추가했습니다.

prev는 현재 노드의 이전 노드를 가리키는 weak_ptr입니다.

main 함수에서는 head, second, third 세 개의 노드를 생성하고 next와 prev 포인터로 연결합니다.

이번에도 third 노드에서 head 노드를 가리키는 것을 볼 수 있습니다.

프로그램을 실행해보면 다음과 같은 출력을 확인할 수 있습니다.

Node 1 created.
Node 2 created.
Node 3 created.
Node 1 destroyed.
Node 2 destroyed.
Node 3 destroyed.

세 개의 노드가 모두 생성된 후 main 함수가 끝나면서 head, second, third가 소멸됩니다.

그리고 head가 참조하는 노드는 third에서도 참조되고 있으므로 reference count가 2인 상태로 남아있게 됩니다.

또한 second는 head를 가리키는 prev 포인터를 가지고 있지만 weak_ptr을 사용했기 때문에 head가 소멸됨에 따라 prev 포인터는 무효화됩니다.

이로 인해 Node 1 destroyed., Node 2 destroyed. 메시지가 출력되지 않습니다.

이와 같이 shared_ptr이 더 복잡한 순환 참조 문제를 일으킬 수 있으므로, 이러한 문제가 발생하지 않도록 설계를 잘 해야 합니다.

순환 참조를 방지하기 위해서는 weak_ptr을 사용하거나, shared_ptr 대신 unique_ptr을 사용하거나, 참조를 다른 객체에게 위임하는 방식을 사용하는 등의 방법이 있습니다.

 

2. shared_ptr 에 raw pointer 를 대입할 때 발생하는 문제

 

#include <memory>
#include <iostream>

struct Node {
    std::shared_ptr<Node> next;
    int value;
    Node(int val): value(val) {
        std::cout << "Node " << value << " created." << std::endl;
    }
    ~Node() {
        std::cout << "Node " << value << " destroyed." << std::endl;
    }
};

int main() {
    std::shared_ptr<Node> head = std::make_shared<Node>(1);
    std::shared_ptr<Node> second = std::make_shared<Node>(2);
    
    head->next = second;
    second->next = head;

    std::shared_ptr<Node> third(head->next.get()); // raw pointer를 대입
    
    return 0;
}

이 예제에서는 third라는 새로운 shared_ptr을 생성할 때, head의 next 포인터를 가리키는 raw pointer를 사용합니다.

이렇게 되면 third와 head는 같은 객체를 참조하게 됩니다. 따라서 head와 second도 같은 객체를 참조하게 됩니다.

프로그램을 실행해보면 다음과 같은 출력을 확인할 수 있습니다.

Node 1 created.
Node 2 created.

두 개의 노드가 생성된 후 main 함수가 끝나면서 head, second, third가 소멸됩니다.

그러나 head와 second는 같은 객체를 참조하고 있으므로 reference count가 2인 상태로 남아있게 됩니다.

이로 인해 Node 1 destroyed., Node 2 destroyed. 메시지가 출력되지 않습니다.

이와 같이 raw pointer를 사용하여 shared_ptr이 같은 객체를 참조하게 되면, 객체가 제대로 해제되지 않는 문제가 발생할 수 있습니다. 이러한 문제를 방지하기 위해서는 raw pointer를 사용하는 것을 지양하고,

가능하면 shared_ptr 또는 weak_ptr을 사용하여 참조 관리를 해주는 것이 좋습니다.

 

3.  std::enable_shared_from_this 를 상속받아야 shared_ptr 의 안전한 사용이 가능한 이유

 

C++11에서 std::enable_shared_from_this를 상속받는 클래스는 해당 객체가 std::shared_ptr에 의해 관리되는 경우, std::shared_ptr가 객체를 안전하게 참조할 수 있도록 해줍니다.

이를 통해 shared_ptr이 안전하게 사용될 수 있는 이유는 다음과 같습니다.

std::shared_ptr은 객체의 reference count를 관리하며, 객체가 참조하는 모든 std::shared_ptr이 소멸될 때 해당 객체도 소멸됩니다. 그러나 이 때 참조하는 모든 std::shared_ptr이 소멸되어 reference count가 0이 되어야만 객체가 소멸되기 때문에, shared_ptr에 대한 순환 참조 문제가 발생할 수 있습니다.

즉, 객체가 참조하는 shared_ptr과 객체 자체를 참조하는 shared_ptr이 서로를 참조하는 순환 참조 문제가 발생할 수 있습니다.

 

std::enable_shared_from_this는 이러한 순환 참조 문제를 해결하기 위해, 해당 클래스에서 shared_from_this() 함수를 제공합니다. 이 함수는 std::shared_ptr의 인스턴스를 생성하면서 해당 객체를 가리키는 포인터를 반환합니다.

이 함수는 해당 객체가 std::shared_ptr에 의해 참조될 때 호출되어 std::shared_ptr의 내부 참조 카운트를 증가시킵니다.

또한 이 함수는 해당 객체가 이미 std::shared_ptr에 의해 관리되고 있지 않은 경우, std::bad_weak_ptr 예외를 발생시킵니다.

따라서, std::enable_shared_from_this를 상속받는 클래스에서 shared_from_this() 함수를 사용하여 객체를 안전하게 참조하면,

순환 참조 문제를 방지하면서 std::shared_ptr에 의해 안전하게 관리될 수 있습니다.

 

< std::enable_shared_from_this >

std::enable_shared_from_this 클래스는 std::shared_ptr에서 객체의 안전한 참조 계수(reference counting)를 관리하기 위한 기능을 제공합니다. 이 클래스는 std::shared_ptr를 사용하여 동적으로 할당된 객체의 참조 계수를 추적하는 데 필요한 일부 메커니즘을 구현합니다.

보통 std::shared_ptr를 사용하여 객체를 참조하는 경우, 해당 객체를 참조하는 모든 std::shared_ptr의 참조 계수가 1씩 증가하고, std::shared_ptr가 객체의 스코프를 벗어나면 참조 계수가 1씩 감소합니다. 이러한 메커니즘은 객체의 안전한 수명 관리를 보장합니다.

그러나 클래스 내부에서 std::shared_ptr의 사용을 제어하기 위해서는 std::enable_shared_from_this 클래스를 상속받아야 합니다. 이를 상속받은 클래스에서는 shared_from_this() 멤버 함수를 사용하여 this 포인터로부터 std::shared_ptr 인스턴스를 생성할 수 있습니다. 이렇게 생성된 std::shared_ptr은 해당 객체를 참조하는 다른 std::shared_ptr 인스턴스와 함께 안전하게 참조 계수를 관리할 수 있습니다.

따라서 std::enable_shared_from_this 클래스는 객체가 안전하게 참조되는 것을 보장하기 위한 용도로 사용됩니다.

#include <memory>
#include <iostream>

class MyClass : public std::enable_shared_from_this<MyClass>
{
public:
    MyClass() { std::cout << "MyClass constructor" << std::endl; }
    ~MyClass() { std::cout << "MyClass destructor" << std::endl; }

    std::shared_ptr<MyClass> getSharedPtr() { return shared_from_this(); }
};

int main()
{
    std::shared_ptr<MyClass> p1(new MyClass());
    std::shared_ptr<MyClass> p2 = p1->getSharedPtr();

    std::cout << "p1 use_count: " << p1.use_count() << std::endl; // 2
    std::cout << "p2 use_count: " << p2.use_count() << std::endl; // 2

    p1.reset();
    std::cout << "p2 use_count after reset p1: " << p2.use_count() << std::endl; // 1

    p2.reset();
    std::cout << "End of program" << std::endl;

    return 0;
}

위 코드에서 MyClass는 std::enable_shared_from_this<MyClass>를 상속받습니다.

그리고 getSharedPtr() 함수는 shared_from_this() 함수를 호출하여 현재 객체를 가리키는 std::shared_ptr을 반환합니다.

main() 함수에서는 p1과 p2라는 두 개의 std::shared_ptr 인스턴스를 생성합니다.

p1은 new MyClass()로 생성된 객체를 가리키고, p2는 p1의 getSharedPtr() 함수를 호출하여 생성된 std::shared_ptr을 가리킵니다.

그리고 p1의 reference count를 출력하면 2가 출력되는 것을 볼 수 있습니다.

이는 p1과 p2가 동일한 객체를 가리키기 때문입니다.

이후 p1을 reset() 메소드를 통해 소멸시키면, p2의 reference count가 1이 되는 것을 확인할 수 있습니다.

마지막으로 p2를 reset() 메소드를 통해 소멸시키면, MyClass 객체도 함께 소멸됩니다.

이때 MyClass 객체의 소멸자가 호출되며, "MyClass destructor" 메시지가 출력됩니다.

 

 

std::enable_shared_from_this를 상속받지 않은 클래스에서 shared_from_this() 함수를 사용하는 예제

#include <memory>
#include <iostream>

class MyClass
{
public:
    MyClass() { std::cout << "MyClass constructor" << std::endl; }
    ~MyClass() { std::cout << "MyClass destructor" << std::endl; }

    std::shared_ptr<MyClass> getSharedPtr() { return shared_from_this(); }
};

int main()
{
    std::shared_ptr<MyClass> p1(new MyClass());
    std::shared_ptr<MyClass> p2 = p1->getSharedPtr();

    std::cout << "p1 use_count: " << p1.use_count() << std::endl; // 1
    std::cout << "p2 use_count: " << p2.use_count() << std::endl; // 1

    p1.reset();
    std::cout << "p2 use_count after reset p1: " << p2.use_count() << std::endl; // 1

    p2.reset();
    std::cout << "End of program" << std::endl;

    return 0;
}

위 코드에서 MyClass는 std::enable_shared_from_this<MyClass>를 상속받지 않습니다. 그리고 getSharedPtr() 함수는 shared_from_this() 함수를 호출하여 현재 객체를 가리키는 std::shared_ptr을 반환합니다.

main() 함수에서는 p1과 p2라는 두 개의 std::shared_ptr 인스턴스를 생성합니다. p1은 new MyClass()로 생성된 객체를 가리키고,

p2는 p1의 getSharedPtr() 함수를 호출하여 생성된 std::shared_ptr을 가리킵니다.

그러나 MyClass 클래스는 std::enable_shared_from_this<MyClass>를 상속받지 않았기 때문에 shared_from_this() 함수가 사용될 때 문제가 발생합니다. 이 예제에서 shared_from_this() 함수를 호출하면, std::bad_weak_ptr 예외가 발생합니다.

따라서 main() 함수에서 p1과 p2의 reference count는 모두 1인 상태이며, 둘 다 reset() 메소드를 호출하면 객체가 제대로 소멸됩니다.

 

4. 사용자 지정 deleter 를 사용

std::shared_ptr에서 사용자 정의 deleter를 지정하여 메모리 할당과 해제를 처리하는 예제입니다.

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass(int n = 0) : num(n) {}
    void print() { std::cout << "MyClass::num = " << num << std::endl; }
private:
    int num;
};

void custom_deleter(MyClass* p) {
    std::cout << "Custom deleter called" << std::endl;
    delete p;
}

int main() {
    std::shared_ptr<MyClass> sp1(new MyClass(1), custom_deleter);
    std::shared_ptr<MyClass> sp2(new MyClass(2), [](MyClass* p) { std::cout << "Lambda deleter called" << std::endl; delete p; });
    
    sp1->print();
    sp2->print();
    
    return 0;
}

위 예제에서는 std::shared_ptr를 사용하여 MyClass 객체를 생성합니다.

std::shared_ptr의 생성자에서 두 번째 인자로 사용자 정의 deleter를 지정하고 있습니다.

첫 번째 std::shared_ptr는 함수 custom_deleter를, 두 번째 std::shared_ptr는 람다 함수를 사용하여 deleter를 지정하고 있습니다.

custom_deleter 함수는 객체를 삭제하기 전에 "Custom deleter called"라는 메시지를 출력합니다.

람다 함수는 객체를 삭제하기 전에 "Lambda deleter called"라는 메시지를 출력합니다.

따라서, std::shared_ptr를 통해 객체를 참조하다가 std::shared_ptr의 수명이 끝나면 사용자 정의 deleter가 호출되어 객체를 삭제합니다.

 

 

+ Recent posts