56

最近レビューしたコードの一部で、 で問題なくコンパイルされましたが、 fromg++-4.6を作成しようとする奇妙な試みに遭遇しました。std::shared_ptrstd::unique_ptr

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

これは私にはかなり奇妙に思えます。std::shared_ptr<Foo>(std::move(foo));私は動きに完全に精通しているわけではありませんが、これはよく知られているはずです(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: 普遍的にコンパイル可能ではない

4

3 に答える 3

33

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


更新:バグ レポートを送信しました。次のコードは、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 は問題、リーク、ヒープの破損を報告しません。変。

の代わりにa への右辺値参照を取る ctor を使用s_ptrして作成すると、適切な動作が示されます。shared_ptrunique_ptrmake_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 のコンストラクターに転送されます。unique_ptr<T>&&T がコンパイル エラーであることを受け入れることができる ctor を持っていない場合。

ただし、このコードを修正して必要なものを取得するのは簡単です (オンライン デモ)。

#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には、 を受け入れるctor があります。の ctor で (13) をunique_ptr<Y,Deleter>&&参照してください。shared_ptr

于 2013-09-19T14:35:24.817 に答える
5

これは、clang が正しくコンパイルされない縮小された例です。

struct ptr
{
  int* p;

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

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

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

Clang 3.4 では次のことが許可されていません。

int i = int(u);

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

int* p = new int(u);

どちらも断るべきだと思います。(Clang 3.3 と ICC は両方を許可します。)

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

于 2013-09-19T22:15:21.043 に答える