5

値をコピーする関数を作成しているとします。

template<class ItInput, class ItOutput>
void copy(ItInput i, ItOutput o) { *o = *i; }

代入は無意味なので、同じオブジェクトを指している場合は代入を避けたいと思います。io

と は異なる型である可能性があり、異なるコンテナーを指している可能性があるため (したがって比較できないif (i != o) { ... }ため) 、明らかに、私は言うことができません。あまり明白ではありませんが、オーバーロードされた関数テンプレートも使用できません。同じ型であっても、反復子が異なるコンテナーに属している可能性があるためです。io

これに対する私の最初の解決策は次のとおりです。

template<class ItInput, class ItOutput>
void copy(ItInput i, ItOutput o)
{
    if (&*o != static_cast<void const *>(&*i))
        *o = *i;
}

しかし、これが機能するかどうかはわかりません。*oまたは*i実際に参照の代わりにオブジェクトを返すとどうなりますか?

これを一般的に行う方法はありますか?

4

4 に答える 4

4

これが本当に必要だとは思いません: 代入が高価な場合、型は (比較的安価な) 自己代入チェックを実行する代入演算子を定義して、不要な作業を防ぐ必要があります。しかし、これは興味深い質問であり、多くの落とし穴があるため、回答することに挑戦します。

入力イテレータと出力イテレータで機能する一般的なソリューションを組み立てる場合、注意しなければならない落とし穴がいくつかあります。

  • 入力イテレータはシングルパス イテレータです。イテレータを介して間接参照を実行できるのは、要素ごとに 1 回だけです。そのため、イテレータを介して間接参照を 1 回実行して、ポイント先の値のアドレスを取得し、2 回目に実行することはできません。コピー。

  • 入力反復子はプロキシ反復子の場合があります。プロキシ イテレータはoperator*、参照ではなくオブジェクトを返すイテレータです。プロキシ イテレータを使用すると、 が右辺値であるため、式&*itの形式が正しくありません*it( unary- をオーバーロードすることは可能です&が、そうすることは通常、悪で恐ろしいと見なされ、ほとんどの型はこれを行いません)。

  • 出力反復子は出力にのみ使用できます。それを介して間接化を実行し、結果を右辺値として使用することはできません。「指された要素」に書き込むことはできますが、そこから読み取ることはできません。

したがって、「最適化」を行う場合は、両方の反復子が前方反復子である場合にのみ行う必要があります (これには、双方向反復子とランダム アクセス反復子が含まれます。それらも前方反復子です)。

私たちは親切なので、概念要件に違反しているという事実にもかかわらず、多くのプロキシ反復子がカテゴリを誤って伝えているという事実にも注意する必要があります。プロキシされたオブジェクトの。(これを行わずに効率的なイテレータを実装する方法もわかりませんstd::vector<bool>。)

次の標準ライブラリ ヘッダーを使用します。

#include <iterator>
#include <type_traits>
#include <utility>

is_forward_iterator型が「実際の」前方反復子であるかどうか (つまり、プロキシ反復子ではないかどうか) をテストするメタ関数 を定義します。

template <typename T>
struct is_forward_iterator :
    std::integral_constant<
        bool,
        std::is_base_of<
            std::forward_iterator_tag,
            typename std::iterator_traits<T>::iterator_category
        >::value &&
        std::is_lvalue_reference<
            decltype(*std::declval<T>())
        >::value>
{ };

can_compare簡潔にするために、2 つの型が両方とも前方反復子であるかどうかをテストするメタ関数 も定義します。

template <typename T, typename U>
struct can_compare :
    std::integral_constant<
        bool,
        is_forward_iterator<T>::value &&
        is_forward_iterator<U>::value
    >
{ };

次に、copy関数の 2 つのオーバーロードを記述し、SFINAE を使用して反復子の型に基づいて適切なオーバーロードを選択します。両方の反復子が前方反復子である場合はチェックを含め、そうでない場合はチェックを除外して常に実行します割り当て:

template <typename InputIt, typename OutputIt>
auto copy(InputIt const in, OutputIt const out)
    -> typename std::enable_if<can_compare<InputIt, OutputIt>::value>::type
{
    if (static_cast<void const volatile*>(std::addressof(*in)) != 
        static_cast<void const volatile*>(std::addressof(*out)))
        *out = *in;
}

template <typename InputIt, typename OutputIt>
auto copy(InputIt const in, OutputIt const out)
    -> typename std::enable_if<!can_compare<InputIt, OutputIt>::value>::type
{
    *out = *in;
}

パイと同じくらい簡単!

于 2012-07-26T05:26:44.213 に答える
3

これは、関数で期待する型に関するいくつかの仮定を文書化し、完全に汎用的ではないことに満足しなければならない場合があると思います。

のようoperator*に、operator&あらゆる種類のことを行うためにオーバーロードできます。を防御している場合は、 およびなどoperator*を考慮する必要があります。operator&operator!=

(コード内のコメントまたは concept/static_assert のいずれかを使用して) 強制するための適切な前提条件はoperator*、反復子が指すオブジェクトへの参照を返し、コピーを実行しない (またはすべきではない) ことです。 . その場合、コードはそのままで問題ないようです。

于 2012-07-26T02:13:40.377 に答える
1

あなたのコードはそのままでは、間違いなく大丈夫ではありません。少なくとも、すべてのイテレータ カテゴリに対して大丈夫ではありません。

入力イテレータと出力イテレータは、初回以降は逆参照可能である必要はなく (シングルパスであることが期待されます)、入力イテレータは「変換可能なT」ものなら何でも逆参照できます (§24.2.3/2)。

したがって、すべての種類の反復子を処理したい場合、この「最適化」を強制することはできないと思います。つまり、2 つの反復子が同じオブジェクトを指しているかどうかを一般的にチェックすることはできません。入力イテレータと出力イテレータを差し控えても構わないと思っている場合は、問題ありません。それ以外の場合は、いずれにせよコピーを実行することに固執します (これについて別のオプションがあるとは思いません)。

于 2012-07-26T02:35:52.853 に答える
0

イテレータが異なる型の場合にequals自動的に戻るヘルパー テンプレート関数を記述します。falseそれか、copy関数自体の特殊化またはオーバーロードを行います。

それらが同じ型である場合は、それらが解決するオブジェクトのポインターを比較するトリックを使用できます。キャストは必要ありません。

if (&*i != &*o)
    *o = *i;

*iorが参照を返さない場合*oでも、問題ありません。必要がなくてもコピーが行われますが、害はありません。

于 2012-07-26T02:42:22.467 に答える