【问题标题】:Destructor is called when I push_back to the vector当我 push_back 到向量时调用析构函数
【发布时间】:2014-03-14 22:30:27
【问题描述】:

我有这个类定义:

class FlashStream
{
public:
    explicit FlashStream(const char * url, vector<uint8> * headers, vector<uint8> * data, void * ndata, void * notifyData = NULL, uint32 lastModified = NULL);
    ~FlashStream();
private:        
    NPStream      _stream;
    // ...
}

(NPStream description)

及其实现:

FlashStream::FlashStream(const char * url, vector<uint8> * headers, vector<uint8> * data, void * ndata, void * notifyData, uint32 lastModified)
{
    // ...
    memset(&_stream, 0, sizeof(NPStream));

    _stream.headers = new char[data->size()]; 

    memcpy((void*)_stream.headers, &(*data)[0], data->size());
    // ...
}

FlashStream::~FlashStream()
{
    // ...
    if(_stream.headers)
        delete [] _stream.headers;
    _stream.headers = NULL;
    // ...
}

现在,当我运行这段代码时:

// ...
vector<FlashStream> _streams;
// ...
_streams.push_back(FlashStream(url, headers, data, _npp.ndata, notifyData, lastModified));
// ...

有时我在FlashStream 的析构函数中的delete [] _stream.headers; 有一个错误,当我将push_back() 调用到vector&lt;FlashStream&gt; _streams 时会调用它。

我读过this question on SO 和其他一些,但仍然不知道如何优雅有效地解决问题。可能问题出在复制构造函数中,但我不知道如何为NPStream.headersNPStream.url 分配内存?

【问题讨论】:

  • 在将临时FlashStream 实例复制到向量后,可能会调用析构函数。我认为要正常工作,您还应该提供正确的(深度)复制/移动行为。
  • 非常感谢,但我不知道如何在为NPStream.headersNPStream.url 分配内存的情况下制作FlashStream 复制构造函数?
  • 你为什么要为这些班级成员使用指针?
  • 我不明白这个问题。
  • 我看到它是一个 C-API 结构。只需创建将指针初始化为类的普通字段所需的所有内容(如std::stringstd::vector&lt;uint8_t&gt; 等),并使用它们的数据地址初始化NPStream 成员中的指针。

标签: c++ vector destructor


【解决方案1】:

此声明:

_streams.push_back(FlashStream(url, headers, data, _npp.ndata, notifyData, lastModified));

相当于:

{
    FlashStream temp(url, headers, data, _npp.ndata, notifyData, lastModified);
    _streams.push_back(temp);
    // temp gets destroyed here
}

因此,您创建了一个临时 FlashStream 对象,该对象被复制到向量中,然后被销毁。您可以通过在 C++11 中使用 emplace_back() 来避免这种情况:

_streams.emplace_back(url, headers, data, _npp.ndata, notifyData, lastModified);

【讨论】:

  • 我使用 VS 2010 并且必须执行 _streams.emplace_back(FlashStream(url, headers, data, _npp.ndata, notifyData, lastModified)); 并且它调用析构函数 ...
  • 这与使用push_back 几乎相同,调用emplace_back 的正确方法(不创建临时对象)是:_streams.emplace_back(url, headers, data, _npp.ndata, notifyData, lastModified)。 VS2010可能没有适当的支持。
  • 当我使用_streams.emplace_back(url, headers, data, _npp.ndata, notifyData, lastModified); 编译器会产生错误:函数vector::emplace_back() 或类似的收入参数太多,因为在我的VS 中只存在emplace_back(FlashStream &amp;&amp;_Val) 和@ 987654331@
  • 那么您可能需要添加一个默认构造函数,该构造函数会为您提供无效的 FlashStream,然后在向量中创建它,例如通过调用_streams.resize(_streams.size() + 1);,并使用放置新构造或非构造函数初始化函数“完成工作”,例如:_streams.back().init(url, headers, data, _npp.ndata, notifyData, lastModified);
  • 这很有趣,但不方便并且与规则相矛盾,即构造函数是对象初始化。但是,谢谢。
【解决方案2】:

可以在两种情况下调用 push_back 处的析构函数。

如前所述,第一种情况发生在您将临时对象推送到向量时。这不是最好的主意,因为将调用构造函数、复制构造函数和析构函数。您可以在向量中存储指向对象的指针以避免冗余调用。

C++ 11 附带的新功能可能会对您有所帮助。

为避免不必要的复制,请使用 std::vector::emplace_back(Args&&... args)

它在向量中就地构造对象而不是复制。

您也可以使用 push_back(value_type&& val) 的移动版本。只需在您的类中定义移动构造函数,移动版本的 push_back 将自动用于临时对象。

调用析构函数的第二种情况是达到向量的容量。 Vector 有两个主要值:大小和容量。大小是当前保存在向量中的元素数量。容量是以元素类型为单位测量的内部向量存储的大小。因此,当您推回元素时,您正在增加矢量的大小。如果存储大小不足以推送新元素,vector 执行重新分配以增加其容量。在重新分配后,向量使用复制构造函数重新构造其对象并使用析构函数删除旧对象。所以,在 psuh_back 向量可以多次调用对象的析构函数。

为了减少在 puhs_back 重新调整向量大小的成本,请始终使用 std::vector::reserve 方法为您的对象预分配存储空间。

std::vector<int> vec;
vec.reserve(20);
for(int i = 0; i<20; ++i)
    vec.push_back(i)

您还可以通过定义移动构造函数来降低对象复制的成本。

class C
{
public:
    C(int c)
        : m_c(c)
    {
        std::cout << "C(int c)" << std::endl;
    }
    C(C&& c)
        : m_c(c.m_c)
    {
        std::cout << "C(C&& c)" << std::endl;
    }
    C(const C& c)
        : m_c(c.m_c)
    {
        std::cout << "C(const C& c)" << std::endl;
    }
    ~C()
    {
        std::cout << "~C()" << std::endl;
    }
private:
    int m_c;
};

int main()
{
    std::vector<C> vc;
    for (int i = 0; i < 100; ++i)
        vc.push_back(C(i));
    return 0;
}

如果您编译并运行它,您将看到根本没有调用“C(const C& c)”。由于定义了构造函数的移动版本,因此 push_back 和 reallocate 将移动您的对象而不是复制。

【讨论】:

    【解决方案3】:

    当您向后推时,您正在创建一个临时对象。
    他就是它的析构函数被调用的那个。

    【讨论】:

      【解决方案4】:

      将对象的副本放在矢量/地图等中是个坏主意。当临时对象被销毁时调用的析构函数。每当 Vector/Map 调整大小或重新排列时,都会再次调用对象的析构函数。

      为了避免这些,您应该存储指向这些对象的指针。您可能想在此处使用 shared_ptrs。

      【讨论】:

      • 我不太确定使用 shared_ptr 是否是正确的做法,以修复 OP 的设计缺陷...
      • shared_ptr 并不是万能的。它不会防止错误的复制构造函数等导致的错误,但总是比按值存储对象更好。
      猜你喜欢
      • 2019-03-22
      • 2019-07-04
      • 2018-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多