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;
}

ご覧のとおり、学生タイプのポインタを無限ループで初期化しています。ポインタメモリの割り当てを解除していないため、すべてのメモリが占​​有されるまでリソースが割り当てられ続けます。

このプログラムを長時間実行しないでください。そうしないと、メモリオーバーフローが原因でシステムがハングする可能性があります。

ただし、スマートポインターを使用した場合、各ループの後にメモリの割り当てが自動的に解除されます。したがって、一度に 1つのポインタメモリしか占有されていなかったでしょう。

C++ のスマートポインタの種類

C++ には 3 種類のスマートポインターがあります。これらは、スマートポインタ用に C++ ライブラリに実装されているタイプです。

unique_ptr

このスマートポインタータイプを使用すると、基になる raw ポインターに 1 人のユーザーのみを割り当てることができます。つまり、同じ基になるポインタを 2つ以上のオブジェクトに割り当てることはできません。

メモリを共有できるようになるまで、デフォルトでこのタイプのポインタを使用する必要があります。

shared_ptr

名前が示すように、このスマートポインターを使用して、複数の所有者を raw ポインターに割り当てることができます。これは、参照カウンターを使用して、担当者の数を追跡します。

weak_ptr

このスマートポインタタイプは、参照カウンタに参加しないことを除いて、shared_ptr と非常によく似ています。参照カウンターがないという意味ではありませんが、参照はカウントされません。

代わりに、その参照カウンターを使用して、他の共有参照をカウントします。所有権を厳密に制御する必要がない場合は、このポインターを使用できます。

1つ以上の 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++ でスマートポインタを使用する場合

すべてにスマートポインタを使用するべきではありません。スマートポインタにはクラステンプレートを使用しているため、一般的にパフォーマンスが低下し、プログラムが複雑になります。

ベストプラクティスとして、パフォーマンスが重要なアプリケーションや小さなプログラムには raw ポインタを使用する必要があります。ただし、ポインタリソースの使用が終了した後は、常にポインタリソースの割り当てを解除する必要があります。

コードが大きく、各ポインタのメモリを手動で割り当てるのが難しい場合は、スマートポインタを使用する必要があります。アプリケーションのパフォーマンスが重要でない場合は、スマートポインターの使用をお勧めします。

例外、参照カウント、割り当て解除時間の実行を処理する必要がある場合は、スマートポインターを使用する必要があります。

関連記事 - C++ Pointer