13

同じポインターを 2 つの異なる に送信するshared_ptrのは良くありません。次のように二重の割り当て解除が発生します。

int* p = new int;
std::shared_ptr<int> p1(p);
std::shared_ptr<int> p2(p); // BAD

次の方法で同じ目的を達成できますstd::enable_shared_from_this

class Good: public std::enable_shared_from_this<Good>
{
public:
    std::shared_ptr<Good> getptr() {
        return shared_from_this();
    }
};

int main()
{
    std::shared_ptr<Good> gp1(new Good);
    std::shared_ptr<Good> gp2 = gp1->getptr();
}

しかし、それでも次のことを防ぐことはできません。

class Good: public std::enable_shared_from_this<Good>
{
public:
    std::shared_ptr<Good> getptr() {
        return shared_from_this();
    }
};

int main()
{
    Good* p = new Good;
    std::shared_ptr<Good> gp3(p);
    std::shared_ptr<Good> gp4(p); // BAD
}

次のようなコードがある場合、これは問題になる可能性があります。

void Function(std::shared_ptr<Good> p)
{
    std::cout << p.use_count() << '\n';
}

int main()
{
    Good* p = new Good;
    std::shared_ptr<Good> p1(p);
    Function(p);    // BAD
}

スマート ポインターがあるのに、なぜ通常のポインターを使用するのでしょうか。パフォーマンスが重要なコード (または便宜上) では、shared_ptr または weak_ptr のオーバーヘッドが望ましくないためです。

この間違いを防ぐために、次のことを行いました。

class CResource : public shared_ptr<Good>
{
public:
    CResource()
    {
    }

    CResource(std::shared_ptr<CBaseControl> c)
        : CResource(c)
    {
    }

private:
    CResource(CBaseControl* p)
    {
    }
};

void Function(CResource p)
{
    std::cout << p.use_count() << '\n';
}

int main()
{
    Good* p = new Good;
    CResource p1(std::shared_ptr<Good>(p));
    Function(p);    // Error
}

これにより、誰かFunctionshared_ptr. ただし、誰かが宣言することを妨げるものではありませんが、それはありvoid Function(std::shared_ptr p)そうもないと思います。

これでもダメですか?これを行うより良い方法はありますか?

4

2 に答える 2

29

解決策は簡単です。最初から生のポインターが独自のメモリを持っていないことです。このパターン:

int* p = new int;
std::shared_ptr<int> p1(p);
std::shared_ptr<int> p2(p); // BAD

単に存在すべきではありません。コードベースから根絶してください。newC++11 で正当な唯一の場所は、スマート ポインター (または非常に低レベルのもの)へのコンストラクター呼び出しへの引数としてです。

つまり、次のようなコードがあります。

std::shared_ptr<int> p1(new int);

または、さらに良いこと (裸のnew関与はもうありません):

auto p1 = std::make_shared<int>();

コード内で生のポインターを使用しても問題ないことに注意してください (しかし、私が見たほとんどの C++ コードでさえ疑問に思います)。ただし、生のポインターを使用する場合は、それらにメモリを所有させないでください。自動ストレージを指す (リソース管理は必要ありません) か、 を使用してそのメンバー関数unique_ptrを介して生のポインターにアクセスします。get

于 2012-06-27T10:17:00.843 に答える
10

この例はコンパイルさえしません:

void Function(std::shared_ptr<Good> p)
{
    std::cout << p.use_count() << '\n';
}

int main()
{
    Good* p = new Good;
    std::shared_ptr<Good> p1(p);
    Function(p);    // BAD
}

shared_ptrその発生を正確に停止するためのexplicitコンストラクターがあります。

そのコンパイルを行うには、次のように記述する必要があります。

    Function( std::shared_ptr<Good>(p) );

これは明らかに間違っており、誰かがその間違いを犯す場合、同じようにそうする可能性があります。

    Function( CResource(std::shared_ptr<Good>(p)) );

では、なぜわざわざ書いたのCResourceですか?それは何を追加しますか?

Konrad Rudolphの優れた答えを拡張するには:

問題を回避する方法に関するあなたの質問への答えは、RAIIのイディオムに従うことですが、完全に従うことです。

コンパイルさえしない例は無視して、その上の例を見てみましょう。

Good* p = new Good;
std::shared_ptr<Good> gp3(p);
std::shared_ptr<Good> gp4(p); // BAD

そのコードは RAII イディオムに従っていません。リソースを取得します。

    Good* p = new Good;

ただし、RAII タイプを初期化しないでください。悪い

次に、既存のリソースでオブジェクトを初期化します。

    std::shared_ptr<Good> gp3(p);

これも悪いです。リソースを取得すると同時に RAII タイプを初期化する必要があります。

次に、同じエラーを繰り返します。

    std::shared_ptr<Good> gp4(p); // BAD

この行を「BAD」とマークしましたが、実際には前の 2 行も同様に悪いものでした3 行目では未定義の動作が発生しますが、最初の 2 行ではエラーが発生する可能性がありました。Good*ぶらぶらしたことがないなら、それを使用して初期化することはできなかったでしょgp4う。shared_ptr<Good> gp4(gp3.get())

ルールは非常に単純です。生のポインタを取得して . に配置しないでくださいshared_ptr。割り当てていない生のポインタはリソースの取得ではないため、初期化には使用しないでください。同じことが の内部にも当てはまりますFunction。生のポインターを取得して、それを使用してshared_ptr型の所有権を取得する を初期化するべきではありません。

これは C++ であるため、すべての間違ったコードの書き方から保護することはできません。十分にやる気のある馬鹿が自分の足を撃つことを防ぐことはできませんが、すべてのガイドラインとツールはそれを防ぐためにそこにあります。ガイドライン。

生のポインタを渡すことによって所有権を譲渡することを強制するインターフェイスがある場合は、インターフェイスを置き換えてunique_ptrfor 所有権の転送を使用するか、それがまったく不可能な場合は、 for を使用してより安全なバージョンでインターフェイスをラップしてみてunique_ptrください所有権の譲渡、および最後の手段としてのみ、危険なインターフェイスを使用しますが、所有権が譲渡されることを非常に明示的に文書化します。

于 2012-06-27T11:37:40.403 に答える