15

私の質問shared_ptrは、バグが含まれていると思われるGCC4.7.2での代入演算子テンプレートの実装に関するものです。

前提1:C++11標準

これが私が話している代入演算子テンプレートの署名です:

template<class Y> shared_ptr& operator=(const shared_ptr<Y>& r) noexcept;

C ++ 11標準(20.7.2.2.3)から:

「に相当し shared_ptr(r).swap(*this)ます。」

つまり、代入演算子テンプレートはコンストラクターテンプレートで定義されます。コンストラクターテンプレートの署名は次のとおりです。

template<class Y> shared_ptr(const shared_ptr<Y>& r) noexcept;

C ++ 11標準(20.7.2.2.1)から:

「必要条件:Y*が暗黙的にT*に変換可能でない限り、[...]コンストラクターはオーバーロード解決に参加してはなりません。」

前提2:GCC 4.7.2の実装:

これで、GCC 4.7.2のコンストラクターテンプレートの実装は私には正しいように見えます(std::__shared_ptrの基本クラスですstd::shared_ptr):

template<typename _Tp1, typename = 
typename std::enable_if<std::is_convertible<_Tp1*, _Tp*>::value>::type>
__shared_ptr(__shared_ptr<_Tp1, _Lp>&& __r) noexcept
    : 
    _M_ptr(__r._M_ptr), 
    _M_refcount()
{
    _M_refcount._M_swap(__r._M_refcount);
    __r._M_ptr = 0;
}

ただし、GCC4.7.2の代入演算子テンプレートの実装は次のとおりです。

template<typename _Tp1>
__shared_ptr& operator=(const __shared_ptr<_Tp1, _Lp>& __r) noexcept
{
    _M_ptr = __r._M_ptr;
    _M_refcount = __r._M_refcount; // __shared_count::op= doesn't throw
    return *this;
}

私が驚いたのは、この操作がコンストラクターテンプレートや。の観点から定義されていないswap()ことです。特に、単純な割り当てでは、型との変換可能性が明示的にチェックされている_M_ptr = __r._M_ptr場合と同じ結果は得られません(これは特殊化できます)。_Tp1*_Tp*std::is_convertible

前提3:VC10の実装

私は、VC10がこの点でより適合した実装を持っていることに気づきました。これは正しいと考えており、テストケースで期待どおりに動作します(GCCはそうではありません)。

template<class _Ty2>
_Myt& operator=(const shared_ptr<_Ty2>& _Right)
{
    // assign shared ownership of resource owned by _Right
    shared_ptr(_Right).swap(*this);
    return (*this);
}

質問

GCC 4.7.2の実装に実際にバグはありshared_ptrますか?この問題のバグレポートは見つかりませんでした。

ポストスクリプト

私のテストケースが何であるか、なぜ私がこの一見重要でない詳細を気にするのか、そしてなぜ私が専門にする必要があることを暗示しているように見えるのかを私に尋ねたい場合は、std::is_convertibleチャットでそうしてください。それは長い話であり、誤解されることなくそれを要約する方法はありません(その不快な結果のすべてを伴う)。前もって感謝します。

4

2 に答える 2

19

私が驚いたのは、この操作がコンストラクター テンプレートの観点からも定義されていないswap()ことです。

そうである必要はありません。それらの用語で定義されているかのように動作する必要があるだけです。

特に、単純な代入では、型とが(これは特殊化できます)によって変換可能かどうかが明示的にチェック_M_ptr = __r._M_ptrされる場合と同じ結果にはなりません。_Tp1*_Tp*std::is_convertible

私は同意しません: [meta.type.synop]/1 この節で定義されたクラス テンプレートのいずれかに特殊化を追加するプログラムの動作は、特に指定がない限り未定義です。

したがって、 and の意味を変更することはできません。is_convertible<Y*, T*>ifY*が変換可能である場合T*、代入は機能し、(ポインターと refcount オブジェクトの) 両方の代入がnoexcept最終結果であるため、スワップと同等です。ポインターが変換可能でない場合、割り当てはコンパイルに失敗しますがshared_ptr(r).swap(*this)、そうであるため、それでも同等です。

私が間違っている場合は、バグ レポートを提出してください。修正しますが、適合プログラムが libstdc++ 実装と標準の要件との違いを検出できるとは思いません。とは言っても、 の観点から実装するように変更することに異論はありませんswap。現在の実装はshared_ptrBoost 1.32から直接導入されたものです。Boost が今でも同じように行うのか、それとも現在使用しているのかはわかりませんshared_ptr(r).swap(*this)

[完全な開示、私は libstdc++ のメンテナーであり、コードの主な責任者です。このshared_ptrコードは、当初は親切にも作者によって寄贈され、boost::shared_ptrその後、私によって削除されました。]

于 2013-01-08T18:43:19.730 に答える
6

GCCでの実装は、標準の要件に準拠しています。ある関数の動作が別の関数セットと同等であると標準が定義している場合、前者の効果は、(実装されているのではなく)標準で定義されている後者の関数の効果と同等であることを意味します。

std::is_convertibleこの標準では、そのコンストラクターを使用する必要はありません 。コンストラクターにはSFINAEが必要ですが、代入演算子にはSFINAEは必要ありません。タイプが変換可能であるという要件は、の実装ではなくプログラムに課せられ、std::shared_ptrそれはあなたの責任です。渡された型が変換できない場合は、プログラムのバグです。is_convertibleその場合、テンプレートを特殊化して使用を無効にしたい場合でも、実装はコードを受け入れる必要があります。

繰り返しになりますが、標準では明示的に許可されていないベーステンプレートのセマンティクスを変更しているため、ポインターis_convertibleの変換を制限することに特化することは未定義の動作です。

これは、答えたくない元の質問につながります。このソリューションについて考えさせられたユースケースは何ですか。あるいは、別の言い方をすれば、なぜ人々は解決したい実際の問題ではなく、解決策について質問し続けるのでしょうか。

于 2013-01-08T18:43:08.393 に答える