push_back和insert之间的std :: vector不一致崩溃(end(),x)

ele*_*ice 21 c++ vector visual-studio-2010

将此代码放入MS Visual C++ 2010,编译(调试或发布),它将为insert()循环而不是push_back循环崩溃:

#include <vector>
#include <string>

using std::vector;
using std::string;

int main()
{
   vector<string> vec1;
   vec1.push_back("hello");

   for (int i = 0; i != 10; ++i)
      vec1.push_back( vec1[0] );

   vector<string> vec2;
   vec2.push_back("hello");

   for (int i = 0; i != 10; ++i)
      vec2.insert( vec2.end(), vec2[0] );

   return 0;
}
Run Code Online (Sandbox Code Playgroud)

问题是push_back()和insert()都通过引用获取新项目,并且当向量重新分配更多空间时,新项目在插入之前变为无效.

海湾合作委员会也应该有这个问题.我没有检查过Clang,但它取决于它使用的是哪个STD库.

MSVC2010在push_back()中有一些额外的代码,用于检测新项目是否实际上是向量中的项目.如果是这样,它会记录项目的索引,并在分配内存后使用它来插入项目(而不是使用现在无效的引用) - 使用_Inside(_STD addressof(_Val))

MSVC的额外代码是非标准的吗?

我担心的是我不知道我用什么代码做过像vec.push_back(vec [1])这样的代码; 或vec.insert(it,vec [2]); 我必须查看使用push_back和insert的数百行代码,这只是我自己的代码......第三方库也可能受到影响.

我假设GCC可以使用这种技术以可怕的方式死亡(我看到没有额外的代码来处理这种情况,但是valgrind在我的简单示例中没有检测到它因此将更难测试),

如何最好地检测并避免犯这个错误?

MSVC2010的额外push_back()代码是非标准的吗?当MSVC找到以这种方式使用的向量时,它应该检测并断言吗?(即安全计算计划)

我正在考虑攻击MSVC2010和GCC的标题以检测这些情况.

还有其他想法吗?

谢谢,保罗

PS:请注意,如果您可以保证向量不需要调整大小,则此用法非常精确(且高效)

ele*_*ice 5

好的,我在virtualbox上安装了Win8 + MSVC2012来试用它.Geez Windows 8使用鼠标很烦人,没有任何按钮可以推动只是悬停,这对于窗口中的屏幕来说很难.

结果很有趣,但仍然不一致恕我直言.

MSVC 2010:这个bug来自移动语义,正如ecatmur建议的那样.

问题是v.insert(v.end(),v [0]); 将选择插入(it,T && val)方法,这在两个方面是错误的:1)它可能导致v [0]的破坏.它似乎没有,这告诉我,const和引用被保留,新版本是通过复制而不是移动创建的.2)代码路径在调整向量大小之前不会复制val.

请注意,由于push_back(&&)中的额外代码(黑客?),问题没有及早发现 - 请参阅底部与MSVC2012相关的进一步评论.

(请注意,insert(it,const&)将在调整向量大小之前首先正确复制新项目,因此如果选择了正确的方法,则根本没有问题).

在MSVC 2012中,通过正确选择insert(it,const T&val)方法来解决这个问题,但是你仍然可以看到push_back()有一些额外的代码来"修复"不正确的用法.

考虑这个测试:

#include <vector>
#include <string>

using std::vector;
using std::string;

int main()
{
   vector<string> vec1;
   vec1.push_back("hello");

   for (int i = 0; i != 1000; ++i)
   {
       string temp = vec1[0];
      vec1.push_back( std::move(vec1[0]) );
   }

   vector<string> vec2;
   vec2.push_back("hello");

   for (int i = 0; i != 1000; ++i)
   {
       string temp = vec2[0];
      vec2.insert( vec2.end(), std::move(vec2[0]) );
   }

   return 0;
}
Run Code Online (Sandbox Code Playgroud)

在这两种情况下,std :: move()用于强制选择&& move方法.在这两种情况下,代码都应该导致灾难,并希望崩溃.

但是,在MSVC 2012中,push_back()循环工作正常,因为push_back(&&)中有一些额外的代码可以检测_Val是否与向量位于同一地址空间,如果是,则会复制而不是移动.但是,如果新项目不是严格地在同一个内存空间但仍然是原始向量的一部分(例如pimpl指针)怎么办?我可以想象让push_back(&&)像它应该的那样死的方法.

当然这实际上并不是必要的,如果程序员说std :: move()那就应该发生什么,对吗?额外检查肯定会使用一些不必要的CPU周期.

insert()循环没有这个hack,这也意味着错误地使用std :: move()只会导致腐败.就个人而言,我更喜欢快速失败而不是失败 - 当你向客户展示时.

所以......解决方案......

  1. 不要使用v.insert(v.end(),v [0])或类似的.这是一个不合理的要求,因为第三方代码(例如Boost,VTK,QT,tbb,xml库等)可能正在使用数百万行代码中的某个地方.我使用的所有第三方库,我都重新编译,所以无论我的代码受到什么影响,它们都会受到影响.

  2. 升级到MSVC 2012 RC.我将不得不等到它成为黄金,然后它将按预期工作(在其他部分有新的和令人兴奋的错误).

  3. 破解标头以检测使用情况.我已经这样做了,但是检测工作的唯一时间就是代码实际运行的时间.

  4. 破解标题以修复插入(&&).(并重新编译所有库/项目 - 叹气).最简单的方法是简单地注释掉插入(&&)变体(然后我们又回到了预C++ 11性能).另一种方法是使用相同的push_back(&&)hack,虽然我不认为这是一种可靠的方法.也许push_back(&&)也应该被注释掉.

进一步更新: 我修复了标题.结果很简单......

MSVC2010的insert(&&)声明如下所示:

template<class _Valty>
iterator insert(const_iterator _Where, _Valty&& _Val)
Run Code Online (Sandbox Code Playgroud)

MSVC2012的插入(&&)删除了模板部分,现在看起来像这样:

iterator insert(const_iterator _Where, _Ty&& _Val)
Run Code Online (Sandbox Code Playgroud)

所以我只是从MSVC2010的insert()中删除了模板化的_Valty,现在选择了正确的方法.它现在也匹配如何声明push_back(&&)(即参数上没有模板).对于emplace*(&&)方法,仍然存在模板化参数,但是没有const和混淆.