【问题标题】:How do you create a static template member function that performs actions on a template class?如何创建对模板类执行操作的静态模板成员函数?
【发布时间】:2010-10-04 02:08:19
【问题描述】:

我正在尝试创建一个从 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 的后减量可以解决这个问题。我还修复了迭代器比较和第二个循环中 compareIter 初始化中的错误。

更新 2:

我看到这个问题又得到了赞成票,所以我想用一个更好的算法来更新它,它使用了一些 C++14 的优点。我的前一个只有在向量中存储的类型实现 operator== 并且它需要一堆副本和不必要的比较时才有效。而且,事后看来,没有必要让它成为一个类的成员。这种新算法允许自定义比较谓词,在发现重复项时缩小比较空间,并显着减少副本数量。名称已更改为erase_duplicates,以更好地符合 STL 算法命名约定。

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));
}

【问题讨论】:

  • 把定义和声明放在一起,即将函数体移动到头部。很快有人会发布一些关于导出的内容,但基本上编译器不会生成代码,直到它看到使用类型实例化的模板。或类似的东西。
  • 您的实现并不安全,因为您在修改容器的同时将迭代器保留在 for 循环中使用。
  • 迭代器不需要知道对容器的修改(出于性能原因),如果某些修改导致容器释放/分配内存,迭代器可能会结束指向无效内存。跨度>
  • @xhantt: vector::erase() 承诺将序列中早期元素的所有迭代器保持不变,因此只要将其称为 vectorToUpdate.erase(compareIter--) 你是安全的.
  • 我可能已经完成了 compareIter = vectorToUpdate.erase(compareIter);因为这可能更直接一些。

标签: c++ stl templates vector static-members


【解决方案1】:

简答

在头文件中定义函数,最好在类定义中。

长答案

在 .cpp 中定义模板函数意味着它不会将#included 获取到任何翻译单元中:它只会在它定义的翻译单元中可用。

因此RemoveVectorDuplicates 必须在标头中定义,因为这是编译器可以文本替换模板参数的唯一方法,因此实例化模板,生成一个可用的类。

有两种解决方法可以解决这种不便

首先,您可以从 .cpp 中删除 #include "foo.h",然后在 headerend 中添加另一个:

#include "foo.cpp"

这使您可以一致地组织文件,但不提供单独编译的通常优势(更小的依赖项、更快和更少的编译)。

其次,您可以在 .cpp 中定义模板函数,并为所有将要使用的类型显式实例化它。

例如,这可以放在 .cpp 的末尾,以使函数可以与 ints 一起使用:

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

但是,这假设您只使用模板来节省一些输入,而不是提供真正的通用性。

【讨论】:

  • 很好的答案,最终回答了关于 SO 的这个(和相关)问题,除了是一种解决方法,因此这可以应用于其他场景。
【解决方案2】:

您有一个替代方法是首先std::sort() 向量,然后使用预先存在的std::unique() 函数删除重复项。排序需要 O(nlog n) 时间,之后删除重复项只需要 O(n) 时间,因为所有重复项都出现在一个块中。您当前的“all-vs-all”比较算法需要 O(n^2) 时间。

【讨论】:

  • 这是我个人拙见的最佳答案。直截了当,使用可靠且有效的经过验证的代码并且非常优雅。 @bsruth,您应该简单地连续调用这两个函数。你可以将它封装在一个内联方法中,让它整洁。
【解决方案3】:

您不能在 .cpp 文件中实现模板函数。完整的实现必须在任何实例化的地方都可见。

只需在标题的类定义中定义函数即可。 这是实现模板函数的常用方法。

【讨论】:

  • 不正确。请参阅stackoverflow.com/questions/115703/… // 虽然,对此的支持在 C++ 的整个生命周期中有所不同。接受的答案描述了官方方法:告诉您的 C++ 编译器要创建哪些实例化。有些编译器可能不需要。
【解决方案4】:

我建议使用更“通用”的方法,而不是传递一个容器,只接收两个迭代器。

类似 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 用于避免 O(n^2) 副本的解决方案(即使运行时间总体上仍为 O(n^2))。 (请注意,相比之下,OP 的解决方案中对 erase() 的每次调用都会将 所有 个后续对象向下移动一个位置。)
【解决方案5】:

与您的问题无关(已经解释过),为什么这是一个静态函数而不是全局驻留在命名空间中?这有点 C++ 风格。

【讨论】:

  • 这与从遗留代码库迁移有关。最终,我打算按照你的建议去做。
【解决方案6】:

我不认为代码可以编译......

vectorToUpdate.erase where std::vector* vectorToUpdate.... 有没有其他人注意到应该有 & 的地方有 *?该代码绝对没有被编译。如果要使用指向向量的指针,则必须使用“->”而不是“。”我知道这实际上有点挑剔,但它指出编译器甚至不关心你的代码......

【讨论】:

  • 你是对的,我从源文件中复制错误,将 * 更改为 &
  • @bsruth:你还需要去掉底部对 Foo::RemoveVectorDuplicates() 的调用中的“&”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-22
  • 1970-01-01
  • 2013-05-30
相关资源
最近更新 更多