emplace_back 当循环同一个列表

Posted

技术标签:

【中文标题】emplace_back 当循环同一个列表【英文标题】:emplace_back when loop of the same list 【发布时间】:2013-01-18 13:02:06 【问题描述】:

我向你展示我的问题

我有 2 个列表,分别命名为 A 和 B。

list<vector<int> > A = 1,2,3;
list<vector<int> > B = 4,5,6; 

我想要的是 A = 1,4,1,5,1,6,2,4,2,5,2,6,3 ,4,3,5,3,6 不使用任何 tmp 列表。

我在 Ubuntu 12.04 上使用 C++11 和 gcc 4.6.3

这样最小化代码:

auto A_begin = A.begin();
auto A_end = A.end();
auto B_begin = B.begin();
auto B_end = B.end();

for(auto i = A_begin; i != A_end; ++i) //loop on A

    for (auto j = B_begin;j != B_end; ++j) //loop on B
    
        vector<int> tmp = (*i); // A[i]
        copy((*j).begin(),(*j).end(),back_inserter(tmp)); // append B[j] to A[i]
        A.emplace_back(tmp); //add it to A
    

A.erase(A_begin,A_end); // remove 1,2,3

所以,我认为算法没问题,但它会在 A 上产生无限循环。 我认为是当我制作 A.emplace_back 时 A_end 发生了变化,但我保存了它,所以我真的不知道在这里追加什么。

我的代码来识别问题:

auto A_begin = A.begin();
auto A_end = A.end();
auto B_begin = B.begin();
auto B_end = B.end();

int ii = A.size();

for(auto i = A_begin; i != A_end; ++i) //loop on A

    for (auto j = B_begin;j != B_end; ++j) //loop on B
    
        vector<int> tmp = (*i);
        A.emplace_back(tmp);
    
    cout<<--ii<<endl; // exit when print 0 ?

这个打印负数,我又要^C了。

编辑:我找到了解决方案:

auto A_begin = A.begin();
auto A_end =  A.end();
auto B_begin = B.begin();
auto B_end = B.end();

list<vector<int>> tmp_l;

for(auto i = A_begin; i != A_end; ++i) //loop on A

    for (auto j = B_begin;j != B_end; ++j) //loop on B
    
        vector<int> tmp = (*i); // A[i]
        copy((*j).begin(),(*j).end(),back_inserter(tmp)); // append B[j] to A[i]
        tmp_l.emplace_back(move(tmp)); //add it to A
    

 swap(tmp_l,A);

【问题讨论】:

【参考方案1】:

这两行:

vector<int> tmp = (*i); // A[i]
copy((*j).begin(),(*j).end(),tmp.end()); // append B[j] to A[i]

将调用未定义的行为。通过复制到 tmp.end(),您只是在 A[i] 结束后覆盖内存,而不是扩展 A[i]。您需要使用 back_insert 迭代器,例如:

vector<int> tmp = (*i); // A[i]
copy((*j).begin(), (*j).end(), back_inserter(tmp)); // append B[j] to A[i]

您还需要包含标题以获取 back_inserter。

编辑:此外,A_end 迭代器指向列表的“结束”位置,因此无论您添加多少项,它们总是添加到 A_end 前面,因此是无限循环。我不确定是否有解决此问题的好方法。不创建临时列表没有任何好处,无论哪种方式,您都在分配相同的内存,只需写入一个新列表即可。

【讨论】:

感谢 back_inserter,但没有变化:/ 这正是我所怀疑的。但是,tmp 列表的副本会很长(我里面有很多对象),所以我尽量不复制它。 tmp 列表不是副本,而是列表。将输出写入一个新列表,然后使用 std::swap 交换 A 和 tmp 的内容。这是一个非常便宜的操作,它不会复制内容,只需移动几个指针。然后当你的函数退出时 tmp 将包含 A 的旧内容并被破坏。 我添加了一个编辑来发布解决这个问题的解决方案,没有 tmp。【参考方案2】:

你的算法不好。

这个:

copy((*j).begin(),(*j).end(),tmp.end());

会导致各种问题,因为你覆盖了一些随机内存。

你可能想做这样的事情来追加:

vector<int> tmp = (*i);
copy((*j).begin(),(*j).end(),std::back_inserter(tmp));

【讨论】:

谢谢,但是没有usign copy,我也有同样的问题。 呼叫保留将不起作用。复制完成后,向量的大小不会更新,因此您仍然要写到向量的末尾。【参考方案3】:

编辑:我找到了解决方案:

该解决方案很好,使用临时向量并将其与 A 交换比在原地执行它更好,因为您的原始版本(以及复制到向量末尾)以 erase 结尾,它移动 每个 元素。

但您的解决方案可以改进:

// get rid of these iterators, they're useless
/*
auto A_begin = A.begin();
auto A_end =  A.end();
auto B_begin = B.begin();
auto B_end = B.end();
*/

list<vector<int>> tmp_l;

// use new for loops
for (auto& a : A)

    for (auto& b : B)
    
// use auto
        auto tmp = a; // A[i]
// I find just inserting at the end simpler than using `back_inserter`
        tmp.insert(tmp.end(), b.begin(), b.end()); // append B[j] to A[i]
// then clear it the moved-from elements:
        b.clear();
// move the tmp vector into place, do not copy it.
        tmp_l.emplace_back(std::move(tmp));
    

swap(tmp_l,A);

【讨论】:

很好,使用 move 是个好主意,但是 b 在循环内,所以,它是第一个 a,而不是其他的。

以上是关于emplace_back 当循环同一个列表的主要内容,如果未能解决你的问题,请参考以下文章

C++的emplace_back函数介绍

为什么emplace_back比push_back更快?快是有条件的

std::vector emplace_back 可以从向量本身的元素复制构造吗?

如何使用向量的 emplace_back 函数? [关闭]

学习 emplace_back() 和 push_back 的区别 emplace_back效率高

默认构造函数阻止调用 emplace_back