【问题标题】:Am I using the copy_if wrong?我是否使用了 copy_if 错误?
【发布时间】:2016-09-18 12:25:23
【问题描述】:

我正在使用 Visual Studio 2010,我正在尝试使用 std::copy_if, 我想复制所有满足谓词的值。例如:

struct comp
{
    bool operator()(const int i) { return i == 5 || i == 7; }
};

int main()
{
    array<int, 10> arr =  { 3, 2, 5, 7, 3, 5, 6, 7 };
    vector<int> res;
    copy_if(arr.begin(), arr.end(), res.begin(), comp());

    for(int i = 0; i < res.size(); i++)
    {
        cout << res[i] << endl;
    }

    return 0;
}

但是当我运行这段代码时,我得到:vector iterator not incrementable。

【问题讨论】:

    标签: c++ predicate


    【解决方案1】:

    copy_if 算法看起来像这样(取自 MSVC2010):

    template<class InIt, class OutIt, class Pr> inline
    OutIt copy_if(InIt First, InIt Last, OutIt Dest, Pr Pred)
    {
        for (; First != _Last; ++First)
            if (Pred(*_First))
                *Dest++ = *First;
        return (Dest);
    }
    

    正如你所见,copy_if 并没有执行 push_back,它只是将值复制到迭代器所在的位置,然后递增迭代器。 你想要使用的是 std::back_inserter,它将元素推回你的向量。如果您使用的是 MSVC2010,您可以使用 Lambda 代替函数对象,Microsoft 将其作为扩展提供 (C++0x)

    int main()
    {
        array<int, 10> arr =  { 3, 2, 5, 7, 3, 5, 6, 7 };
        vector<int> res;
        copy_if(arr.begin(), arr.end(), back_inserter(res),[](const int i) { return i == 5 || i == 7; });
    
        for(unsigned i = 0; i < res.size(); i++)
            cout << res[i] << endl;
    
        return 0;
    }
    

    【讨论】:

    • 虽然这个答案得出了正确的结论,但解释却相当混乱。该错误与向量为空无关。该算法只需要一个满足输出迭代器概念的迭代器。此外,在您的最终代码示例中,您使用 lambda 表达式作为谓词,当前标准 C++ 不支持该表达式(并且 OP 未提及 C++0x)。
    • 但是 copy_if 也不是当前标准的一部分,但是 copy_if 和 lambda 都是最近的 C++0x 草案的一部分
    • @hidyat:没错,但是,Microsoft 将其作为自定义扩展提供。如果 OP 没有标记他们的问题 C++0x,你至少应该提到它是你的答案。
    • 我觉得将 C++0x 或 Lambda 称为“自定义”扩展有点奇怪。他们没有任何习俗。从明年开始(当新标准 - 希望 - 发布时)任何不支持它的编译器都将被视为不合格。
    • @Fabio:自定义 可能是个坏词。 @hidayat:请忘记我的第一条评论,这是错误的(正如@visitor 向我解释的那样)。
    【解决方案2】:

    如果要考虑性能问题,请考虑不要使用 std::back_inserter 填充目标向量(一种涉及任意数量的昂贵目标向量重新分配的方法),调用 std::copy_if 后跟源大小的目标向量通过 dest.erase(iteratorReturnedByCopyIf, dest.end()) - 一种方法,涉及先分配一次,然后再重新分配一次erase()。

    数据

    代码

    #include <algorithm>
    #include <chrono>
    #include <functional>
    #include <iostream>
    #include <iterator>
    #include <numeric>
    #include <vector>
    
    long long MeasureMilliseconds(std::function<void()> func, unsigned iterations)
    {
       auto beginTime = std::chrono::high_resolution_clock::now();
       for (unsigned i = 0; i < iterations; ++i)
       {
          func();
       }
       auto endTime = std::chrono::high_resolution_clock::now();
       long long milliseconds = std::chrono::duration_cast<
          std::chrono::milliseconds>(endTime - beginTime).count();
       return milliseconds;
    }
    
    bool IsEven(int i)
    {
       return i % 2 == 0;
    }
    
    int main()
    {
       const unsigned Iterations = 300000;
       for (size_t N = 0; N <= 100; N += 2)
       {
          std::vector<int> source(N);
          // Populate source with 1,2,...,N
          std::iota(std::begin(source), std::end(source), 1);
    
          long long backInserterMilliseconds = MeasureMilliseconds([&]
          {
             std::vector<int> dest;
             std::copy_if(std::begin(source), std::end(source), 
                std::back_inserter(dest), IsEven);
          }, Iterations);
    
          long long sourceSizeAndEraseMilliseconds = MeasureMilliseconds([&]
          {
             std::vector<int> dest(source.size());
             std::vector<int>::iterator copyIfIterator = std::copy_if(
                std::begin(source), std::end(source), std::begin(dest), IsEven);
             dest.erase(copyIfIterator, dest.end());
          }, Iterations);
    
          std::cout << "N=" << N << '\n';
          std::cout << "Default-size dest and back_inserter: " << 
             backInserterMilliseconds << '\n';
          std::cout << "      Source-sized dest and erase(): " << 
             sourceSizeAndEraseMilliseconds << "\n\n";
       }
       return 0;
    }
    

    代码输出

    N=90
    Default-size dest and back_inserter: 469
          Source-sized dest and erase(): 89
    
    N=92
    Default-size dest and back_inserter: 472
          Source-sized dest and erase(): 90
    
    N=94
    Default-size dest and back_inserter: 469
          Source-sized dest and erase(): 92
    
    N=96
    Default-size dest and back_inserter: 478
          Source-sized dest and erase(): 92
    
    N=98
    Default-size dest and back_inserter: 471
          Source-sized dest and erase(): 93
    
    N=100
    Default-size dest and back_inserter: 480
          Source-sized dest and erase(): 92
    

    参考文献

    [alg.copy]
    Qt ScatterChart

    【讨论】:

    • 如果在调用copy_if 之前调用dest.reserve(source.size()),对std::back_inserter 的测试会有什么不同?
    • 在我的机器上,使用 Visual Studio 2017 - Visual C++ 14.1,dest.reserve(source.size()) 实际上稍微快一些...
    【解决方案3】:

    您可以使用输出迭代器:

    copy_if(arr.begin(), arr.end(), std::back_inserter(res), comp());
    

    【讨论】:

    • 虽然您的回答得出了正确的结论,但我发现您的解释相当混乱。您确实不必在向量中插入新项目,只要向量的大小足够,就可以使用copy_if 覆盖现有项目。
    【解决方案4】:

    保留数组大小。 hidayat 给出了原因。

    res.resize(arr.size());
    

    【讨论】:

    • @space:从某种意义上说,调整大小会有所帮助。 Vector 的迭代器可以很好地用作输出迭代器(如果it 指向有效位置,您可以执行*it = n; 好吧)。例如,您可以这样做,使用返回值擦除多余的项目:ideone.com/QKnry。或者您可以使用 count_if 来确定结果向量需要多大。这些是否是好方法是另一个问题。
    • 正如@visitor 解释的那样,您的答案确实是正确的。但是,除非您编辑答案,否则我无法删除反对票。
    • @Space_C0wb0y 我做了一个小修改,让你可以投票
    • 您还需要在复制正确的元素后修剪多余的元素,可能是res.erase(copy_if(...),res.end())。在我看来,使用back_inserter 会更容易理解。
    猜你喜欢
    • 1970-01-01
    • 2014-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多