C++의 스마트 포인터 이해

Mohd Mohtashim Nawaz 2023년10월12일
  1. C++에서 스마트 포인터 구현
  2. 스마트 포인터 소멸자를 사용하여 C++에서 메모리 누수 방지
  3. C++의 스마트 포인터 유형
  4. C++에서 스마트 포인터를 사용하는 경우
C++의 스마트 포인터 이해

이 기사에서는 C++의 스마트 포인터, 메모리 누수를 방지하는 방법, 스마트 포인터의 유형 및 사용해야 하는 경우에 대해 설명합니다.

C++에서 스마트 포인터 구현

C++에서 스마트 포인터를 원래 원시 포인터를 유지하는 데 사용할 수 있는 클래스 템플릿으로 정의할 수 있습니다. 스마트 포인터 클래스에는 원시 포인터, 소멸자 및 연산자 재정의 메서드를 보유하는 포인터 변수가 포함되어 있습니다.

그러나 이러한 필드와 메서드만 포함하는 것으로 제한되지 않습니다. 필요에 따라 메서드를 추가할 수 있습니다.

원시 포인터를 직접 처리하는 대신 이 클래스를 사용하여 포인터를 처리합니다. 그리고 C++에서 스마트 포인터에 대한 사용자 정의 클래스를 정의하는 대신 표준 C++ 라이브러리를 사용할 수도 있습니다.

C++ 스마트 포인터 구현을 위한 사용자 정의 클래스를 정의해 보겠습니다.

#include <iostream>

using namespace std;

template <class T>
class CustomSmartPointer {
  T *data;

 public:
  explicit CustomSmartPointer(T *ptr = NULL)  // Constructor to assign pointer
  {
    data = ptr;
  }
  ~CustomSmartPointer()  // Destructor that deallocated memory
  {
    delete data;
  }
  // We overload * and -> operators
  T *operator->() { return data; }
  T &operator*() { return *data; }
};

int main() {
  CustomSmartPointer<int> myPtr(new int());
  *myPtr = 100;
  cout << *myPtr << endl;
  // After executing above statement, memory allocated to myPtr is deallocated.
  return 0;
}

출력:

stark@stark:~/eclipse-workspace/smart_pointer$ g++ custom_smart_prt.cc
stark@stark:~/eclipse-workspace/smart_pointer$ ./a.out
100

스마트 포인터 소멸자를 사용하여 C++에서 메모리 누수 방지

소멸자의 존재는 스마트 포인터 클래스를 특별하게 만들고 원시 포인터와 구별합니다. C++ 스마트 포인터 소멸자를 사용하여 포인터에 할당된 메모리를 할당 해제합니다.

소멸자는 자동으로 호출되어 클래스 개체가 범위를 벗어날 때 할당된 메모리를 할당 해제합니다. 메모리 할당 해제 프로세스를 자동화했다면 할당 해제를 잊어버릴 수 있는 포인터로 인한 리소스 누수에 대해 걱정할 필요가 없습니다.

스마트 포인터 소멸자는 Java 및 C#과 유사한 C++의 자동 가비지 수집기처럼 작동합니다.

원시 포인터로 인한 리소스 누수의 예를 살펴보겠습니다.

#include <iostream>

using namespace std;

class student {
 private:
  string name;
  int marks;

 public:
  void setMarks(int m) { marks = m; }
  void setName(string n) { name = n; }
  int getMarks() { return marks; }
  string getName() { return name; }
};

int main() {
  while (true) {
    student* p = new student;
    *p->name = "Stark";
    *p->marks = 100;
  }
  return 0;
}

보시다시피 무한 루프에서 학생 유형의 포인터를 초기화하고 있습니다. 포인터 메모리를 할당 해제하지 않았으므로 모든 메모리가 채워질 때까지 리소스를 계속 할당합니다.

우리는 이 프로그램을 오랫동안 실행해서는 안됩니다. 그렇지 않으면 메모리 오버플로로 인해 시스템이 중단될 수 있습니다.

그러나 스마트 포인터를 사용했다면 각 루프 후에 메모리가 자동으로 할당 해제되었을 것입니다. 따라서 한 번에 하나의 포인터 메모리만 점유되었을 것입니다.

C++의 스마트 포인터 유형

C++에는 세 가지 유형의 스마트 포인터가 있습니다. 이들은 스마트 포인터용 C++ 라이브러리에서 구현된 유형입니다.

unique_ptr

이 스마트 포인터 유형을 사용하면 기본 원시 포인터에 대해 단일 사용자만 할당할 수 있습니다. 즉, 두 개 이상의 객체에 동일한 기본 포인터를 할당할 수 없습니다.

메모리를 공유할 수 있을 때까지 기본적으로 이 유형의 포인터를 사용해야 합니다.

shared_ptr

이름에서 알 수 있듯이 이 스마트 포인터를 사용하여 원시 포인터에 여러 소유자를 할당할 수 있습니다. 이것은 참조 카운터를 사용하여 양수인 수를 추적합니다.

weak_ptr

이 스마트 포인터 유형은 참조 카운터에 참여하지 않는다는 점을 제외하고 shared_ptr과 매우 유사합니다. 참조 카운터가 없는 것은 아니지만 참조는 계산되지 않습니다.

대신 참조 카운터를 사용하여 다른 공유 참조를 계산합니다. 소유권에 대한 엄격한 제어가 필요하지 않을 때 이 포인터를 사용할 수 있습니다.

하나 이상의 shared_ptr 개체에 대한 액세스를 제공합니다. 그러나 교착 상태의 가능성을 완화시켜주는 장점이 있습니다.

C++ 스마트 포인터 구현을 살펴보겠습니다.

#include <iostream>
#include <memory>

using namespace std;

class student {
 private:
  string name;
  int marks;

 public:
  void setMarks(int m) { marks = m; }
  void setName(string n) { name = n; }
  int getMarks() { return marks; }
  string getName() { return name; }
};

int main() {
  unique_ptr<student> ptr1(new student);
  ptr1->setName("Stark");
  ptr1->setMarks(100);
  cout << "unique_ptr output >>\n";
  cout << ptr1->getName() << " : " << ptr1->getMarks() << "\n";

  cout << "shared_ptr output >> \n";
  shared_ptr<student> ptr2(new student);
  ptr2->setName("Tony");
  ptr2->setMarks(99);
  cout << ptr2->getName() << " : " << ptr2->getMarks() << "\n";

  shared_ptr<student> ptr22;
  ptr22 = ptr2;
  cout << ptr22->getName() << " : " << ptr22->getMarks() << "\n";
  cout << "Reference count of shared_ptr: " << ptr2.use_count() << endl;

  auto ptr = make_shared<student>();
  ptr->setName("Jarvis");
  ptr->setMarks(98);

  cout << "Weak pointer output >>" << endl;
  weak_ptr<student> ptr3;
  ptr3 = ptr;
  cout << "Reference count of weak_ptr: " << ptr3.use_count() << endl;
  shared_ptr<student> ref = ptr3.lock();
  cout << ref->getName() << " : " << ref->getMarks() << "\n";

  return 0;
}

출력:

stark@stark:~/eclipse-workspace/smart_pointer$ g++ types_smart.cc
stark@stark:~/eclipse-workspace/smart_pointer$ ./a.out
unique_ptr output >>
Stark : 100
shared_ptr output >>
Tony : 99
Tony : 99
Reference count of shared_ptr: 2
Weak pointer output >>
Reference count of weak_ptr: 1
Jarvis : 98

약한 포인터의 참조 횟수는 참조 횟수에 참여하지 않기 때문에 1이라는 점에 유의해야 합니다.

C++에서 스마트 포인터를 사용하는 경우

모든 것에 스마트 포인터를 사용해서는 안 됩니다. 스마트 포인터에 클래스 템플릿을 사용하기 때문에 일반적으로 성능이 저하되고 프로그램이 복잡해집니다.

성능이 중요한 응용 프로그램과 작은 프로그램에는 원시 포인터를 모범 사례로 사용해야 합니다. 그러나 포인터 자원의 사용이 끝난 후에는 항상 포인터 자원을 할당 해제해야 합니다.

코드가 크고 각 포인터에 대한 메모리 할당을 수동으로 해제하기 어려운 경우 스마트 포인터를 사용해야 합니다. 응용 프로그램이 성능에 중요하지 않은 경우 스마트 포인터를 사용하는 것이 좋습니다.

예외, 참조 횟수, 할당 해제 시간 실행을 처리해야 하는 경우 스마트 포인터를 사용해야 합니다.

관련 문장 - C++ Pointer