C++ での unique_ptr の宣言と使用

Shikha Chaudhary 2024年2月16日
  1. C++ での unique_ptr の宣言と使用
  2. C++ の一意のポインター
  3. まとめ
C++ での unique_ptr の宣言と使用

ポインターは、C++ で多くの便利な目的を果たします。 プログラムのスペースの節約からデータ構造の実装の支援まで、ポインターは C や C++ などのプログラミング言語にとって非常に重要なツールです。

この記事では、そのようなポインターの 1つである一意のポインターの概念について説明します。 C++ の標準ライブラリは、スマート ポインターの実装の 1つとしてこのポインターを提供します。

これらのスマート ポインターが現在どうなっているのか知りたいですか? 読む; 私たちはあなたのためにすべてをカバーしました.

C++ での unique_ptr の宣言と使用

ポインターが他の変数のアドレスを格納するために使用されることは既にわかっています。 これらのポインターは多くの場所で役立ちますが、大きな問題があります。

指定されたコード ブロックを見てください。 ほとんどの場合、次のようにポインターを宣言してから使用しますが、削除することはありません。

void demo() {
  int *d = new int;
  *d = 10;
}

これにより、メモリ リークの問題が発生します。 これは、メモリ セクションが使用後に割り当て解除または解放されていない場合に発生します。

このように、他のプログラムで使用できないメモリや空きメモリは、スペースの浪費につながり、パフォーマンスが低下します。 これはメモリリークに他なりません。

プログラムがより複雑になると、ヒープ メモリ全体が使い物にならなくなる可能性さえあります。 したがって、C++11 では、この問題に対処するためにスマート ポインターの概念が考案されました。

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

Java や C# などの言語には、未使用のメモリを自動的に解放するメカニズムがあります。 したがって、プログラマーはポインターの割り当てを解除する必要はありません。これはガベージ コレクション システムが処理します。

C++ の場合、これはスマート ポインターを使用して実現されます。 スマート ポインターは、生のポインターと *-> のようなオーバーロード演算子をラップする単純なクラスに他なりません。

スマート ポインター クラスのオブジェクトは、通常のポインターと同じように見えますが、通常のポインターとは異なり、未使用のメモリ空間を自分で解放できます。 C++ の標準ライブラリの <memory> ヘッダー内には 4 種類のスマート ポインターが定義されており、unique_ptr はその 1つです。

これで、C++ の一意のポインターについて学習を開始する準備が整いました。

C++ の一意のポインター

unique_ptr は、C++ の auto_ptr を置き換えるために開発されました。 生のポインターとは異なり、unique_ptr は生のポインターのコンテナーであり、排他的な所有権とメモリ リークがないことを保証します。

C++ での unique_ptr の宣言

以下のコードは、C++ で一意のポインターを宣言する方法を示しています。

std::unique_ptr<Type> p(new Type);

ここで、整数、たとえば 10 を指す一意のポインターを作成する場合、上記の構文は次のように変更されます。

std::unique_ptr<int> p(new int(10));

上記のコード行は、一意のポインター p が値 10 を指すことを意味します。 この基本的な宣言がどのように機能するかを理解する必要があります。

new 式は、上記のコード行でポインター p を作成します。 しかし、同じ宣言を以下のように変更すると、意味が変わります。

std::unique_ptr<int> p2(x);

ここで、ポインターは変数 x に格納されます。 概念的には、物事は同じです。

両方の場所で unique_ptr を作成していますが、2 番目の方法を使用することはお勧めできません。

2 番目の方法を使用すると、次のようなことができます。

std::unique_ptr<int> p2(x);
std::unique_ptr<int> p3(x);
//....
std::unique_ptr<int> p6(x);

これは、次のセクションで説明するように、複数の一意のポインターが同じオブジェクトを所有していることを意味します。これは、一意のポインターのセマンティクスに反しています。

また、C++14 では、次のように make_unique を使用して unique_ptr を宣言するオプションがあります。

unique_ptr<int> d = make_unique<int>(10);

これにより、値 10 を指す一意のポインター d が作成されます。

unique_ptr のさまざまな機能を 1つずつ見ていきましょう。

C++ unique_ptr の自動削除

生のポインターを使用すると、プログラマーは使用後にポインターを削除するのを忘れることが多く、メモリ リークの問題が発生します。 unique_ptr は、unique_ptr がスコープ外になると保持しているオブジェクトを破棄するため、この手間を省きます。

unique_ptr が破棄されるか、別のポインターが unique_ptr に割り当てられると、オブジェクトは破棄されます。 これは、get_deleter() (ptr) を使用して行われます。これは、delete 演算子を使用してオブジェクトを破棄し、メモリの割り当てを解除します。

したがって、unique_ptr を使用する場合、動的に割り当てられたオブジェクトを削除する必要はなく、unique_ptr のデストラクタがそれを処理します。

以下は、ポインターが破棄される範囲外を示すコードの例です。

void demo() { unique_ptr<int> demo(new int); }
< -- -- -- -- -- -- -- -- -- -- -- -- -scope of the pointer ends here

コントロールがこれらの中括弧の外に出ると、オブジェクトは破棄されます。

C++ unique_ptr の独占的所有権

unique_ptr の次の非常に重要な機能は、オブジェクトに排他的な所有権を提供し、コピーすることはできません。 これは、1つの unique_ptr だけが同時に 1つのオブジェクトだけを指すことができることを意味します。

これが、unique_ptr をコピーできない理由でもあります。 多くの場合、生のポインターでは、通常の代入はポインターのコピーにつながります。

しかし、これは unique_ptr には当てはまりません。 この例を見てください。

ここでは、p1p2 という 2つの一意のポインターがあり、最初のポインターを 2 番目のポインターにコピーまたは割り当てます。

#include <iostream>
#include <memory>

using namespace std;

int main() {
  unique_ptr<int> p1(new int);
  unique_ptr<int> p2 = p1;
  cout << "Success";
  return 0;
}

出力:

use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]'
    8 |     unique_ptr<int> p2 = p1;

コンパイル時にエラーが発生し、print ステートメントが実行されていないことがわかります。 これは、同時に 1つのオブジェクトのみを指す unique_ptr を C++ でコピーできないことを明確に示しています。

ここで、生のポインターでこれを行っていれば、print ステートメントは正常に実行されたはずです。 これは以下で行われます。

#include <iostream>
using namespace std;

int main()

{
  int *ptr1;
  int *ptr2;
  ptr2 = ptr1;
  cout << "Success";
  return 0;
}

出力:

Success

今回は、生のポインターであるため、ポインターがコピーされます。 これは基本的に、重複に関して unique_ptr がどのように機能するかです。

C++ unique_ptr の所有権の譲渡

前に見たように、unique_ptr の所有権をコピーするだけでは変更できません。 ここでの解決策は、C++ の標準ライブラリが提供する move() 関数を使用することです。

代入演算子を使用して unique_ptr をコピーしようとしたのと同じコードを使用しています。 ただし、今回は、この割り当て中に move() 関数を使用します。

#include <iostream>
#include <memory>

using namespace std;

int main() {
  unique_ptr<int> p1(new int);
  unique_ptr<int> p2 = move(p1);  // using the move() function
  cout << "Success";
  return 0;
}

出力:

Success

move() 関数を使用したため、エラーは発生せず、print ステートメントも実行されます。

これにより、オブジェクトの所有権を別の unique_ptr に移すことができますが、いずれにせよ、unique_ptr が指すオブジェクトは 1つずつ残ります。

これらは、生のポインターとは異なる unique_ptr の主な機能です。

一意のポインターの詳細については、この ドキュメントを参照してください。

まとめ

この記事では、C++ のユニーク ポインターの概念について説明しました。 ユニーク ポインターは、その排他的所有権と自動削除機能で知られるスマート ポインターです。

それがどのように宣言されるか、そして move() 関数を使用して一意のポインターの所有権を譲渡する方法を学びました。 機能を理解したので、必要に応じて一意のポインターを使用できるようになることを願っています。

関連記事 - C++ Pointer