web-dev-qa-db-ja.com

テンプレートクラスでアクションを実行する静的テンプレートメンバー関数をどのように作成しますか?

Std :: vectorから重複を削除する汎用関数を作成しようとしています。各ベクトルタイプの関数を作成したくないので、これを任意のタイプのベクトルを受け入れることができるテンプレート関数にしたいと思います。ここに私が持っているものがあります:

//foo.h

Class Foo {

template<typename T>
static void RemoveVectorDuplicates(std::vector<T>& vectorToUpdate);

};

//foo.cpp

template<typename T>
void Foo::RemoveVectorDuplicates(std::vector<T>& vectorToUpdate) {
for(typename T::iterator sourceIter = vectorToUpdate.begin(); (sourceIter != vectorToUpdate.end() - 1); sourceIter++) {
        for(typename T::iterator compareIter = (vectorToUpdate.begin() + 1); compareIter != vectorToUpdate.end(); compareIter++) {
            if(sourceIter == compareIter) {
                vectorToUpdate.erase(compareIter);
            }
        }
    }
}

//SomeOtherClass.cpp

#include "foo.h"

...

void SomeOtherClass::SomeFunction(void) {
    std::vector<int> myVector;

    //fill vector with values

    Foo::RemoveVectorDuplicates(myVector);
}

リンカーエラーが発生し続けますが、正常にコンパイルされます。私が間違っていることについてのアイデアはありますか?

更新:Iraimbilanjaによって与えられた答えに基づいて、私は行ってコードを書き直しました。ただし、誰かがRemoveDuplicates関数を実行するための作業コードを望んでいる場合に備えて、ここに示します

//foo.h

Class Foo {

    template<typename T>
    static void RemoveVectorDuplicates(T& vectorToUpdate){
        for(typename T::iterator sourceIter = vectorToUpdate.begin(); sourceIter != vectorToUpdate.end(); sourceIter++) {
            for(typename T::iterator compareIter = (sourceIter + 1); compareIter != vectorToUpdate.end(); compareIter++) {
            if(*sourceIter == *compareIter) {
                compareIter = vectorToUpdate.erase(compareIter);
            }
        }
    }
};

署名でstd :: vectorを指定すると、イテレーターが正しく機能しないことがわかります。そのため、より一般的なアプローチを採用する必要がありました。また、compareIterを消去すると、ループの次の反復でポインター例外が発生します。消去後のcompareIterのポストデクリメントはその問題を処理します。また、反復子の比較と2番目のループのcompareIterの初期化のバグも修正しました。

更新2:

この質問は別の賛成票を得たので、C++ 14の優れた機能を使用するより優れたアルゴリズムで更新すると考えました。私の以前のものは、ベクターに格納された型がoperator ==を実装していて、大量のコピーと不必要な比較が必要な場合にのみ機能しました。そして、後で見ると、それをクラスのメンバーにする必要はありません。この新しいアルゴリズムにより、カスタム比較述語が可能になり、重複が検出されると比較スペースが縮小され、コピー数が大幅に少なくなります。 STLアルゴリズムの命名規則により準拠するために、名前がerase_duplicatesに変更されました。

template<typename T>
static void erase_duplicates(T& containerToUpdate) 
{
    erase_duplicates(containerToUpdate, nullptr);
}

template<typename T>
static void erase_duplicates(T& containerToUpdate, 
  std::function<bool (typename T::value_type const&, typename T::value_type const&)> pred) 
{
    auto lastNonDuplicateIter = begin(containerToUpdate);
    auto firstDuplicateIter = end(containerToUpdate);
    while (lastNonDuplicateIter != firstDuplicateIter) {
        firstDuplicateIter = std::remove_if(lastNonDuplicateIter + 1, firstDuplicateIter, 
            [&lastNonDuplicateIter, &pred](auto const& compareItem){
            if (pred != nullptr) {
                return pred(*lastNonDuplicateIter, compareItem);
            }
            else {
                return *lastNonDuplicateIter == compareItem;
            }
        });
        ++lastNonDuplicateIter;
    }
    containerToUpdate.erase(firstDuplicateIter, end(containerToUpdate));
}
25
bsruth

簡潔な答え

ヘッダーで、できればクラス定義内で関数を定義します。

長い答え

.cpp内でテンプレート関数を定義することは、#includedをどの翻訳単位にも入れないことを意味します。定義された翻訳単位でのみ使用できます。

したがって、RemoveVectorDuplicatesはヘッダーで定義する必要があります。これは、コンパイラーがテンプレート引数をテキスト置換できる唯一の方法であるため、インスタンス化テンプレートであり、使用可能なクラスを生成します。

この不便さには2つの回避策があります

First、.cppから#include "foo.h"を削除し、別の1つを追加することができますendheaderの:

#include "foo.cpp"

これにより、ファイルを一貫して整理できますが、個別のコンパイルの通常の利点はありません(依存関係が小さくなり、コンパイルが速くなり、まれになる)。

2番目 、. cppでテンプレート関数を定義し、これで使用されるすべての型に対して明示的にインスタンス化できます。

たとえば、これは.cppの最後に移動して、intsで関数を使用できるようにすることができます。

template void Foo::RemoveVectorDuplicates(std::vector<int>*);

ただし、これは、真の汎用性を提供するためではなく、いくつかの入力を節約するためにテンプレートのみを使用することを前提としています。

31
Iraimbilanja

代わりの方法の1つは、最初にベクターをstd::sort()し、次に既存のstd::unique()関数を使用して重複を削除することです。並べ替えにはO(nlog n)時間かかります。その後、重複を削除するには、O(n)時間がかかります。すべての重複が1つのブロックに表示されるためです。現在の "all-vs-all"比較アルゴリズムはO(n ^ 2)時間かかります。

5
j_random_hacker

.cppファイルにテンプレート関数を実装することはできません。完全な実装は、インスタンス化された場所に表示される必要があります。

ヘッダーのクラス定義内で関数を定義するだけです。これが、テンプレート関数を実装する通常の方法です。

2
jalf

コンテナーを渡すのではなく、より「一般的な」アプローチを使用して、2つの反復子を受け取ることをお勧めします。

It remove_duplicates(It first、It last)のようなもので、イテレータを返すので、remove:v.erase(remove_duplicates(v.begin(), v.end()), v.end())のように呼び出すことができます。

template <typename It>
It remove_duplicate(It first, It last)
{
  It current = first;
  while(current != last) {
    // Remove *current from [current+1,last)
    It next = current;
    ++next;
    last = std::remove(next, last, *current);
    current = next;
  }
  return last;
}
1
Ismael

あなたの問題(すでに説明されています)とは無関係に、これが名前空間にグローバルに常駐するのではなく、なぜ静的関数なのですか?これはややC++に似たものになります。

0
Konrad Rudolph

そのコードはコンパイルできないと思います...

vectorToUpdate.erase where std :: vector * vectorToUpdate ....&があるはずの*があることに他の誰かが気づいていますか?そのコードは間違いなくコンパイルされていません。ベクトルへのポインタを使用する場合は、「。」の代わりに「->」を使用する必要があります。私はこれが実際には少しうるさいことを知っていますが、コンパイラがあなたのコードについてさえ気にしていないことを指摘しています...

0
Hippiehunter