web-dev-qa-db-ja.com

unique_ptrからshared_ptrを作成する

_g++-4.6_で正常にコンパイルされた最近レビューしたコードの一部で、_std::shared_ptr_から_std::unique_ptr_を作成しようとする奇妙な試みに遭遇しました:

_std::unique_ptr<Foo> foo...
std::make_shared<Foo>(std::move(foo));
_

これはかなり奇妙に思えます。これはstd::shared_ptr<Foo>(std::move(foo)); afaikである必要がありますが、私は動きに完全には精通していません(そして_std::move_はキャストであり、何も動かないことを知っています)。

このSSC(NUC *)Eの異なるコンパイラーで確認する

_#include <memory>

int main()
{
   std::unique_ptr<int> foo(new int);
   std::make_shared<int>(std::move(foo));
}
_

コンパイルの結果:

  • g ++-4.4.7でコンパイルエラーが発生する
  • g ++-4.6.4はエラーなしでコンパイルします
  • g ++-4.7.3は内部コンパイラエラーを返します
  • g ++-4.8.1でコンパイルエラーが発生する
  • clang ++-3.2.1はエラーなしでコンパイルします

質問は次のとおりです。標準に関してどのコンパイラが正しいのか。標準では、これは無効なステートメント、有効なステートメントである必要がありますか、またはこれは単に未定義ですか?

追加

これらのコンパイラ(clang ++やg ++-4.6.4など)の一部は、許可されていない変換を許可することに同意しました。ただし、g ++-4.7.3(std::make_shared<Foo>(std::move(foo));で内部コンパイラエラーを生成する)では、int bar(std::move(foo));を正しく拒否します。

この振る舞いの大きな違いのため、質問はそのままにしておきますが、その一部はint bar(std::move(foo));への還元で答えられるでしょう。


*)NUC:普遍的にコンパイルできない

49
stefan

UPDATE 2:この バグ は、r191150のClangで修正されました。 GCCは適切なエラーメッセージでコードを拒否します。


UPDATE:バグレポート を提出しました。 clang ++ 3.4(トランク191037)を搭載した私のマシンの次のコード

#include <iostream>
#include <memory>

int main()
{
   std::unique_ptr<int> u_ptr(new int(42));

   std::cout << " u_ptr.get() = " <<  u_ptr.get() << std::endl;
   std::cout << "*u_ptr       = " << *u_ptr       << std::endl;

   auto s_ptr = std::make_shared<int>(std::move(u_ptr));

   std::cout << "After move" << std::endl;

   std::cout << " u_ptr.get() = " <<  u_ptr.get() << std::endl;
   std::cout << "*u_ptr       = " << *u_ptr       << std::endl;
   std::cout << " s_ptr.get() = " <<  s_ptr.get() << std::endl;
   std::cout << "*s_ptr       = " << *s_ptr       << std::endl;
}

これを印刷します:

 u_ptr.get() = 0x16fa010
*u_ptr       = 42
After move
 u_ptr.get() = 0x16fa010
*u_ptr       = 42
 s_ptr.get() = 0x16fa048
*s_ptr       = 1

ご覧のとおり、unique_ptrは移動されていません。標準は、移動後はnullであることを保証しますshared_ptrは間違った値を指します。

奇妙なことに、警告なしにコンパイルされ、valgrindは問題を報告せず、リークもヒープ破損も報告しません。奇妙な。

s_ptrの代わりにshared_ptrへの右辺値参照を取るunique_ptr ctorでmake_sharedを作成すると、適切な動作が示されます。

#include <iostream>
#include <memory>

int main()
{
   std::unique_ptr<int> u_ptr(new int(42));

   std::cout << " u_ptr.get() = " <<  u_ptr.get() << std::endl;
   std::cout << "*u_ptr       = " << *u_ptr       << std::endl;

   std::shared_ptr<int> s_ptr{std::move(u_ptr)};

   std::cout << "After move" << std::endl;

   std::cout << " u_ptr.get() = " <<  u_ptr.get() << std::endl;
   //std::cout << "*u_ptr       = " << *u_ptr       << std::endl; // <-- would give a segfault
   std::cout << " s_ptr.get() = " <<  s_ptr.get() << std::endl;
   std::cout << "*s_ptr       = " << *s_ptr       << std::endl;
}

以下を印刷します。

 u_ptr.get() = 0x5a06040
*u_ptr       = 42
After move
 u_ptr.get() = 0
 s_ptr.get() = 0x5a06040
*s_ptr       = 42

ご覧のとおり、標準で必要な移動後のu_ptrはnullであり、s_ptrは正しい値を指します。これは正しい動作です。


(元の答え。)

Simpleが指摘したように : "Fooがstd :: unique_ptrをとるコンストラクタを持たない限り、コンパイルすべきではありません。"

それを少し拡張するには: make_shared は引数をTのコンストラクターに転送します。 Tにunique_ptr<T>&&を受け入れることができるctorがない場合、それはコンパイルエラーです。

ただし、このコードを修正して必要なものを取得するのは簡単です( online demo ):

#include <memory>
using namespace std;

class widget { };

int main() {

    unique_ptr<widget> uptr{new widget};

    shared_ptr<widget> sptr(std::move(uptr));
}

ポイントは:make_sharedはこの状況で使用するのが間違っていることです。 shared_ptrには、unique_ptr<Y,Deleter>&&を受け入れるctorがあります。 (13)shared_ptr のctorを参照してください。

32
Ali

これはコンパイルできません。ポインタの一意性と共有性をしばらく無視すると、基本的にこれを実行しようとしています。

int *u = new int;
int *s = new int(std::move(u));

これは、動的にintを作成し、std::unique_ptr<int>への右辺値参照で初期化することを意味します。 intsの場合、それは単にコンパイルすべきではありません。

一般クラスFooの場合、クラスに依存します。値、const ref、または右辺値refでstd::unique_ptr<Foo>を取得するコンストラクターがある場合は動作します(ただし、作成者が意図したとおりに動作しない可能性があります)。その他の場合、コンパイルすべきではありません。

12
Angew

Clangが正しくコンパイルされない簡単な例を次に示します。

struct ptr
{
  int* p;

  explicit operator bool() const { return p != nullptr; }
};

int main()
{
  ptr u{};
  int* p = new int(u);
}

Clangは、明示的なブール変換演算子を使用してintを初期化します(Intelコンパイラも初期化します)。

Clang 3.4は以下を許可しません:

int i = int(u);

しかし、それは許可します:

int* p = new int(u);

両方とも拒否されるべきだと思います。 (Clang 3.3とICCは両方を許可します。)

この例を バグレポート に追加しました。

5
Jonathan Wakely