_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));
}
_
コンパイルの結果:
質問は次のとおりです。標準に関してどのコンパイラが正しいのか。標準では、これは無効なステートメント、有効なステートメントである必要がありますか、またはこれは単に未定義ですか?
追加
これらのコンパイラ(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:普遍的にコンパイルできない
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を参照してください。
これはコンパイルできません。ポインタの一意性と共有性をしばらく無視すると、基本的にこれを実行しようとしています。
int *u = new int;
int *s = new int(std::move(u));
これは、動的にint
を作成し、std::unique_ptr<int>
への右辺値参照で初期化することを意味します。 int
sの場合、それは単にコンパイルすべきではありません。
一般クラスFoo
の場合、クラスに依存します。値、const ref、または右辺値refでstd::unique_ptr<Foo>
を取得するコンストラクターがある場合は動作します(ただし、作成者が意図したとおりに動作しない可能性があります)。その他の場合、コンパイルすべきではありません。
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は両方を許可します。)
この例を バグレポート に追加しました。