12

このプログラムを考えてみましょう:

#include <memory>
#include <iostream>

class X
  : public std::enable_shared_from_this<X>
{
public:
  struct Cleanup1 { void operator()(X*) const; };
  struct Cleanup2 { void operator()(X*) const; };
  std::shared_ptr<X> lock1();
  std::shared_ptr<X> lock2();
};

std::shared_ptr<X> X::lock1()
{
  std::cout << "Resource 1 locked" << std::endl;
  return std::shared_ptr<X>(this, Cleanup1());
}

std::shared_ptr<X> X::lock2()
{
  std::cout << "Resource 2 locked" << std::endl;
  return std::shared_ptr<X>(this, Cleanup2());
}

void X::Cleanup1::operator()(X*) const
{
  std::cout << "Resource 1 unlocked" << std::endl;
}

void X::Cleanup2::operator()(X*) const
{
  std::cout << "Resource 2 unlocked" << std::endl;
}

int main()
{
  std::cout << std::boolalpha;

  X x;
  std::shared_ptr<X> p1 = x.lock1();
  {
    std::shared_ptr<X> p2 = x.lock2();
  }
}

C++11 標準セクション 20.7.2 には、これが無効であることを示唆するものは何もありません。2 つのshared_ptrオブジェクトが同じポインタを格納している&xが所有権を共有していないこと、および の存続期間を終了させない「deleter」を使用することは少し珍しいことです*get()が、それを禁止するものは何もありません。shared_ptr(そして、それらのいずれかがまったく意図されていない場合、一部のメンバー関数が値を受け入れる理由を説明するのは困難std::nullptr_tです。) そして、予想どおり、プログラムは次のように出力します。

Resource 1 locked
Resource 2 locked
Resource 2 unlocked
Resource 1 unlocked

しかし、今、少し追加するとmain():

int main()
{
  std::cout << std::boolalpha;

  X x;
  std::shared_ptr<X> p1 = x.lock1();
  bool test1( x.shared_from_this() );
  std::cout << "x.shared_from_this() not empty: " << test1 << std::endl;
  {
    std::shared_ptr<X> p2 = x.lock2();
  }
  try {
    bool test2( x.shared_from_this() );
    std::cout << "x.shared_from_this() not empty: " << test2 << std::endl;
  } catch (std::exception& e) {
    std::cout << "caught: " << e.what() << std::endl;
  }
}

その後、事態はさらに複雑になります。g++ 4.6.3 では、次の出力が得られます。

Resource 1 locked
x.shared_from_this() not empty: true
Resource 2 locked
Resource 2 unlocked
caught: std::bad_weak_ptr
Resource 1 unlocked

2 番目の呼び出しがshared_from_this()失敗するのはなぜですか? 20.7.2.4p7 のすべての要件が満たされています。

Requires: enable_shared_from_this<T>のアクセス可能な基本クラスでなければなりませんT。タイプ*thisのオブジェクトのサブオブジェクトでなければなりません。を所有するインスタンスが少なくとも 1 つ存在する必要があります。tTshared_ptrp &t

TXtxpp1。】

しかし、g++enable_shared_from_thisは基本的に、20.7.2.4p10 の (非規範的な) 「注」から提案された実装に従い、weak_ptrクラスのプライベート メンバーを使用しenable_shared_from_thisます。そして、 でかなり複雑なことをしなければ、この種の問題を説明することは不可能に思えますenable_shared_from_this

これは規格の欠陥ですか? (そうであれば、解決策が「あるべき」ものについてここでコメントする必要はありません。サンプルプログラムが未定義の動作を呼び出すように要件を追加し、そのような単純な実装で十分であることを示唆しないように注を変更します....)

4

4 に答える 4

6

はい、C++11 には欠陥があります。これを許可するには:

2 つの shared_ptr オブジェクトが同じポインター &x を格納しているが所有権を共有していないこと、および *get() の有効期間を終了しない「deleter」を使用することは少し珍しいことですが、それを禁止するものは何もありません。

これは、「削除者」が何をするかに関係なく、未定義の動作であると明示的に述べる必要があります。確かに、そのようにすることは技術的に違法ではないかもしれません。

ただし、コードを使用する人に嘘をついています。shared_ptrを受け取った人は、オブジェクトの所有権を持っていることを期待しています。それ (またはそのコピー)を保持している限り、shared_ptrそれが指すオブジェクトは存在し続けます。

それはあなたのコードには当てはまりません。したがって、構文的には正しいが意味的には無効であると言えます。

の言語shared_from_thisは問題ありません。shared_ptrそれは、変更が必要な言語です。同じポインタを「所有」する 2 つの個別の一意のポインタを作成することは、未定義の動作であると述べる必要があります。

于 2012-04-26T18:40:32.587 に答える
5

これは仕様の穴であり、欠陥であることに同意します。それは基本的にhttp://open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2179と同じですが、その問題はわずかに異なる (そして私見はより明らかに壊れている) 角度から発生します。

shared_ptr問題 2179 のコードとは異なり、no-op デリーターを使用しているため、これが の誤用であることに同意するかどうかはわかりません。shared_ptr問題は、そのような の使用をと組み合わせようとするときだと思いますenable_shared_from_this

だから私の最初の考えは、の要件を拡張することによってそれを修正することでしたshared_from_this:

Requires: enable_shared_from_this<T>のアクセス可能な基本クラスでなければなりませんT。タイプ*thisのオブジェクトのサブオブジェクトでなければなりません。を所有する少なくとも 1 つのインスタンスが存在し、所有する他のインスタンスはと所有権を共有する必要があります。tTshared_ptrp &t shared_ptr&tp

ただし、例はその要件を満たしているため、これでは十分ではありません。2 回目の呼び出しでshared_from_this()は、所有者 ( ) は 1 つしかありませんが、 を呼び出して基本クラスp1の状態を既に「破損」しています。enable_shared_from_thislock2()

プログラムの小さな形式は次のとおりです。

#include <memory>
using namespace std;

int main()
{
  struct X : public enable_shared_from_this<X> { };
  auto xraw = new X;
  shared_ptr<X> xp1(xraw);   // #1
  {
    shared_ptr<X> xp2(xraw, [](void*) { });  // #2
  }
  xraw->shared_from_this();  // #3
}

libstdc++、libc++、および VC++ (Dinkumware) の 3 つすべてが同じように動作し、#3 でスローしbad_weak_ptrます。これは、#2 でweak_ptr<X>基本クラスのメンバーを更新して と所有権を共有するためxp2です。weak_ptr<X>.

興味深いことにboost::shared_ptr、 はスローしません。代わりに、#2 はノーオペレーションであり、#3 は とshared_ptr所有権を共有する a を返しますxp1。これは、上記とほぼ同じ例のバグ レポートに対応して行われました。

于 2015-08-25T10:23:29.590 に答える