在push_back到静态向量之后C++取消定位

C++ Deallocation after push_back to static vector

本文关键字:之后 C++ 取消 定位 向量 静态 push back      更新时间:2023-10-16

在下面的代码中,我试图实现一个缓存的单例模式(每个乘法(n)只存在一次)。

  vector<Multiplication>& Multiplication::cacheVector() {
    static vector<Multiplication> cache({Multiplication(0)});
    return cache;
  }
  const Multiplication& Multiplication::getInstance(unsigned int order) {
    while (order >= cacheVector().size()) {
      cacheVector().push_back(Multiplication(cacheVector().size()));      
    }
    return cacheVector()[order];      
  }

问题是:当我尝试使用缓存的Multiplication实例时,我会得到一个segfault。Valgrind告诉我,我调用无效读取,因为数据在getInstance函数中是空闲的:

==5471==    by 0x4C2054D: std::_Vector_base<tnp::Multiplication, std::allocator<tnp::Multiplication> >::_M_deallocate(tnp::Multiplication*, unsigned long) (stl_vector.h:174)
==5471==    by 0x4C2029D: _ZNSt6vectorIN3tnp14MultiplicationESaIS1_EE19_M_emplace_back_auxIJS1_EEEvDpOT_ (stl_vector.h:430)
==5471==    by 0x4C1FFDF: _ZNSt6vectorIN3tnp14MultiplicationESaIS1_EE12emplace_backIJS1_EEEvDpOT_ (vector.tcc:101)
==5471==    by 0x4C1EE9F: std::vector<tnp::Multiplication, std::allocator<tnp::Multiplication> >::push_back(tnp::Multiplication&&) (stl_vector.h:920)
==5471==    by 0x4C1DF52: tnp::Multiplication::getInstance(unsigned int) (multiplication.cpp:83)

乘法没有自己的复制构造函数和那些字段:

  class Multiplication { 
    const unsigned int order;
    const vector<Product> valueSum;
    const vector<Product> partialDerSum;
    const vector<Multiplication>& instances;

(其中instances是对缓存向量的反向引用,order==0除外),释放可能发生在valueSum和partialDerSum向量上。

那么,为什么C++要对我的新乘法实例进行解除分配呢?它不应该直接复制到向量中吗?我希望避免任何显式的堆分配,因为指针间接会在以后的过程中造成一些性能损失。

您还没有指出导致segfault的确切线路,所以我只是胡乱猜测:

可能您在某个地方存储了对Multiplication实例的引用,之后向向量添加了新项,导致向量重新分配其内部内存,因此您的引用指向已释放的内存

不是它崩溃原因的真正答案,而是对这些问题的解释:

那么,为什么C++要对我的新乘法实例进行解除分配呢?难道不应该只是将其复制到矢量中?

事实上,它确实会复制或移动。

让我们稍微描述一下这一行:

cacheVector().push_back(Multiplication(cacheVector().size()));  

Multiplication(cacheVector().size())被分配在某个地方,但cacheVector的内存不在同一个地方,因此向量在cacheVector中创建实例时复制或移动(这里肯定是移动)Multiplication中的数据。

这里有一个小例子可以帮助你理解这一点。

您可以看到,有一个对"normal"构造函数的调用,然后是对move构造函数的调用。然后,编译必须删除移动到向量中的实例,因此它确实会调用使用"普通"构造函数分配的实例上的析构函数。