web-dev-qa-db-ja.com

なぜ、いつ自分の削除機能を提供する必要があるのですか?

なぜ、いつ私は自分の削除機能を提供する必要があるのですか?キーワードdeleteでは不十分ですか?

スマートポインターを使用して、newによって割り当てられたメモリ以外のリソースを管理する場合は、deleterを渡すことを忘れないでください。


更新:

コメントで尋ねられたように、引用されたテキストと例がはっきりしない理由は、何かについて間違って考えていたためです。これは、スマートポインターについて考えていたのは、動的メモリ管理のためにのみ/関連していたためです。そのため、この例では、スマートポインターを使用して動的でないメモリを管理しているので、混乱します。

先輩からの良い説明:

スマートポインターは、何かが動的メモリであることをまったく気にしません。これは、必要なときに何かを追跡し、範囲外になったときにその何かを破棄する方法にすぎません。ファイルハンドル、ネットワーク接続などについて言及したのは、それらが動的メモリではないことを指摘することでしたが、スマートポインターはとにかくそれらをうまく管理できます。


C++ Primer 5thは、疑似ネットワーク接続(デストラクタを定義しないでください)を使用して説明しています。

悪い:

struct destination; // represents what we are connecting to
struct connection; // information needed to use the connection
connection connect(destination*); // open the connection
void disconnect(connection); // close the given connection
void f(destination &d /* other parameters */)
{
// get a connection; must remember to close it when done
connection c = connect(&d);
// use the connection
// if we forget to call disconnect before exiting f, there will be no way to closes
}

良い:

void end_connection(connection *p) { disconnect(*p); }
void f(destination &d /* other parameters */)
{
connection c = connect(&d);
shared_ptr<connection> p(&c, end_connection);
// use the connection
// when f exits, even if by an exception, the connection will be properly closed
}

完全なコンテキストのスクリーンショット(無関係なテキストをいくつかクリアします):
Smart Pointers and Dumb classes

Smart Pointers and Dumb classes part 2

15
Rick

標準のdeleteが、スマートポインターによってライフタイムが管理されているリソースの割り当て解除、解放、破棄、または破棄に適切でない場合は、スマートポインターの作成に独自の削除を提供する必要があります。

スマートポインターの一般的な用途は、スマートポインターによって管理されているリソースとしてメモリを割り当てることです。これにより、スマートポインターがスコープから外れたときに、管理されているリソース(この場合はメモリ)がdelete演算子。

標準のdelete演算子は2つのことを行います:(1)オブジェクトのデストラクタを呼び出して、割り当てられたメモリが解放または割り当て解除される前に、オブジェクトが必要なクリーンアップを実行できるようにします。(2)割り当てられたメモリを解放します。オブジェクトが作成されたときのオブジェクトの標準のnew演算子。これは、new演算子で発生するのとは逆の順序で、(1)オブジェクトにメモリを割り当て、オブジェクトの構築環境を確立するために必要な基本的な初期化を行い、(2)オブジェクトのコンストラクターを呼び出します。オブジェクトの開始状態を作成します。 を参照してくださいC++ new演算子は、割り当てとctor呼び出し以外に何をしますか?

したがって、独自の削除機能が必要な場合の重要な質問は、「オブジェクトのコンストラクタが呼び出される前に実行されていたアクションのうち、オブジェクトのデストラクタが完了した後に戻す必要があるか」

通常、これは、標準のnew演算子によって行われるような、ある種のメモリ割り当てです。

ただし、new演算子で割り当てられたメモリ以外のリソースの場合、delete演算子の使用は、リソースがnew演算子。

そのため、delete演算子が適切でないこの種のリソースにスマートポインターを使用する場合は、スマートポインターがスコープとトリガーから外れたときに使用する独自の削除メソッドまたは関数または演算子を指定する必要があります。独自のデストラクタは、スマートポインタによって管理されているリソースの破棄を順に処理します。

出力の簡単な例

std::unique_ptr<>を使用した簡単な例を作成し、生成された出力と、ポインターを使用した削除機能の使用と非使用を示し、デストラクタの明示的な使用方法を示します。

単純なWindowsコンソールアプリケーションのソースコードは次のようになります。

// ConsoleSmartPointer.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <memory>
#include <string>
#include <iostream>

class Fred {
public:
    Fred() { std::cout << "  Fred Constructor called." << std::endl; }
    ~Fred() { std::cout << "  Fred Destructor called." << std::endl; }
};
class George {
public:
    George() { std::cout << "   George Constructor called" << std::endl; }
    ~George() { std::cout << "   George Destructor called" << std::endl; }
private:
    int iSomeData;
    std::string  a_label;
    Fred  myFred;
};

void cleanupGeorge(George *)
{
    // just write out a log and do not explicitly call the object destructor.
    std::cout << "  cleanupGeorge() called" << std::endl;
}

void cleanupGeorge2(George *x)
{
    // write out our message and then explicitly call the destructor for our
    // object that we are the deleter for.
    std::cout << "  cleanupGeorge2() called" << std::endl;
    x->~George();    // explicitly call destructor to do cleanup.
}

int func1()
{
    // create a unique_ptr<> that does not have a deleter.
    std::cout << "func1 start. No deleter." << std::endl;

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

    std::cout << "func1 end." << std::endl;
    return 0;
}

int func2()
{
    // create a unique_ptr<> with a deleter that will not explicitly call the destructor of the
    // object created.
    std::cout << "func2 start. Special deleter, no explicit destructor call." << std::endl;

    std::unique_ptr<George, void(*)(George *)> p(new George, cleanupGeorge);

    std::cout << "func2 end." << std::endl;
    return 0;
}

int func3()
{
    // create a unique_ptr<> with a deleter that will trigger the destructor of the
    // object created.
    std::cout << "func3 start. Special deleter, explicit destructor call in deleter." << std::endl;

    std::unique_ptr<George, void(*)(George *)> p(new George, cleanupGeorge2);

    std::cout << "func3 end." << std::endl;
    return 0;
}

int main()
{
    func1();
    func2();
    func3();
    return 0;
}

上記の単純なアプリケーションは、次の出力を生成します。

func1 start. No deleter.
  Fred Constructor called.
   George Constructor called
func1 end.
   George Destructor called
  Fred Destructor called.
func2 start. Special deleter, no explicit destructor call.
  Fred Constructor called.
   George Constructor called
func2 end.
  cleanupGeorge() called
func3 start. Special deleter, explicit destructor call in deleter.
  Fred Constructor called.
   George Constructor called
func3 end.
  cleanupGeorge2() called
   George Destructor called
  Fred Destructor called.

追加投稿

スマートポインターとは何ですか?いつ使用すべきですか?

std :: shared_ptr でのカスタム削除機能の使用

std::make_shared<>を使用した削除機能に関するこのディスカッションと、それが利用できない理由もご覧ください。 make_sharedにdeleterを渡す方法は?

std :: unique_ptrのカスタム削除は、デストラクタの手動呼び出しに有効な場所ですか?

Aにデストラクタがある場合、std :: unique_ptr <A>はいつ特別な削除機能を必要としますか?

C++でのRAIIおよびスマートポインタ

8

(明らかに)deleteがオブジェクトを破棄する方法ではない場合。 placement new で割り当てられたオブジェクトは簡単な例です。

入門書からの例は実際にはかなり良いです(私は 後でそれらを破棄する の後に借りています)が、std::shared_ptr(またはstd::unique_ptr)の別の創造的な使用は、COMオブジェクトの存続期間を管理することです。これらは、deleteを呼び出すのではなく、Release ()メソッドを呼び出すことで割り当てを解除します(そうした場合は、おやすみのウィーン)。

だから、それを説明するために、あなたはそうするかもしれません:

static void release_com_object (IUnknown *obj) { obj->Release (); }

IUnknown *my_com_object = ...
std::shared_ptr <IUnknown> managed_com_object (my_com_object, release_com_object);

ここで基本的な考え方を理解するために、COMについて何も知る必要はありません。一般に、リソースを解放する方法はいくつもあり、適切なカスタム削除プログラムのセットですべてを処理できます。これは本当にクールなトリックです。


ああ、今は本当に溝に入っています。ここにもう1つあります。今回はstd::unique_ptrとラムダを使用します(なぜshared_ptrを使用しているのかわからない-それははるかに高価です)。 std::unique_ptrを使用する場合は、構文が異なることに注意してください。テンプレートに削除者の関数シグネチャを通知する必要があります。

FILE *f = fopen ("myfile", "r");

if (f)
{
    std::unique_ptr <FILE, void (*) (FILE *)> (f, [] (FILE *f) { fclose (f); });
    // Do stuff with f
}   // file will be closed here

ああ、あなたができることはたくさんあります。

ライブデモ

5
Paul Sanders

この例は、型のインスタンスの確定的な存続期間を活用する方法を示しています。それらの破壊時に何が起こるかは、デストラクタによって定義されます(組み込み型は除外され、組み込み型はありません)。デストラクタは、その状態を「クリーンアップ」するタイプの一部です。多くの場合、実行することはそれほど多くありませんが、メモリ割り当ては実際にクリーンアップする必要があり、この例では、切断関数を呼び出す必要があります。これは、リソースを手動で管理するすべてのタイプに当てはまり(メンバー変数の単純な集計または知り合い以外)、この例も同様に適切です。

_class ConnectionHandle {
    public:
        ConnectionHandle(destination& d) : c(connect(d)) {}
        ~ConnectionHandle() { end_connection(c); }
    private:
        connection& c;
};
_

そのような型の存続期間がスマートポインターによって管理される場合、リソースをクリーンアップするためにスマートポインターのデストラクタを使用する可能性の1つであり、それがその例です。これは_std::shared_ptr_と_std::unique_ptr_の両方で機能しますが、後者の場合、カスタム削除機能は型のシグニチャーの一部です(_unique_ptr_を渡すときにさらに入力する)。

この状況をnoカスタム削除が必要な状況と比較することも有益です。

_struct A { int i; std::string str; };

auto sp = std::make_shared<A>(42, "foo");
_

ここでAのリソースはA( "集約")が所有する値であり、クリーンアップは自動的に行われます(iに対して行うことは何もありません。strstd::string::~string())。

3
lubgr

C++では、newを使用して独自のカスタムアロケーターを作成できます。 deleteすべてのnewと同じように、カスタムアロケーターによって割り当てられたすべてのものも削除する必要があります。

これによって引き起こされる問題の具体的な例は、カスタムアロケーターを使用してメモリ予算を追跡する場合です(つまり、すべての割り当てを予算に割り当て、それらの予算のいずれかを超えると警告を表示します)。これがnewdeleteをラップしているとしましょう。スマートポインターがスコープから外れると、deleteだけが呼び出され、カスタムアロケーターはメモリが解放されたことを認識しません。予算に対してメモリ使用量が不正確になります。

同じタイプのラッピングアロケータを使用してリークを検出した場合、deleteを直接呼び出すと誤検知が発生します。

何らかの理由で実際に自分のメモリを手動で割り当てている場合、deleteがメモリを解放しようとすると、非常に時間がかかります。

あなたの例の場合、ネットワーク接続のメモリは、最初にきれいに切断することができずに解放されます。実際のケースでは、その結果として、接続のもう一方の端がタイムアウトになるまでハングするか、接続の切断に関する何らかのエラーが表示されることがあります。

2
Robert Rouhani