initializer_list与2013中的双重删除

Double delete in initializer_list vs 2013

本文关键字:删除 2013 list initializer      更新时间:2023-10-16

今天在我的项目中遇到了一个内存问题,其中一个类使用c++11 initializer_list。系统发出内存问题的信号:"dbgdel.cpp中的Expression _BLOCK_TYPE_IS_VALID(pHead->nBlockUse)。我将代码简化为一个简单的例子,它不再抛出表达式,但问题从调试输出中变得明显。在我看来,这段代码是正确的,而且它似乎可以与g++一起工作。

#include <functional>
#include <memory>
#include <string>
#include <iostream>
#include <vector>
#include <map>
#include <sstream>
#include <initializer_list>
using namespace std;
class B {
public:
    char data[256];
    B(const string& x) {
        cout << "Init " << this << endl;
    }
    B(const B& b) {
        cout << "Copy " << this << endl;
    }
    ~B() {
        cout << "Deleting b " << this << endl;
    }
};
class C {
public:
    vector<B> bs;
    C(initializer_list<B> bb) {
        for(auto& b : bb) {
            bs.push_back(b);
        }
    }
};
int main(int argc, char** argv) {
    C bb { B("foo"), B("bar") };
    return 0;
}

输出为:

初始化00B7FAE8初始化00B7FBE8副本00E108A0复制00E10AE8(????)删除b 00E108A0复制00E10BE8删除b 00B7FBE8删除b 00B7FAE8正在删除b 00B7FAE8(已删除两次!)

我在这里犯了什么错误,或者这不应该起作用?

initializer_list的行为有缺陷。在其析构函数中,它调用整个范围的向量delete(delete[]),然后再次删除数组中的第一个条目。此行为不是initializer_list类的一部分,看起来像是编译器错误。initializer_list没有析构函数,也没有分配用于列表的数组。它看起来就像一个C数组的包装器。

至于使用您看到的额外副本,这是由初始化过程中的向量大小调整引起的。以下是您的流程:

Init 00B7FAE8       // construct "foo"
Init 00B7FBE8       // construct "bar"
Copy 00E108A0       // copy "foo" to vector (capacity=1)
Copy 00E10AE8 (?????) // copy the above object to the resized vector (capacity = 2)
Deleting b 00E108A0   // delete the smaller vector buffer
Copy 00E10BE8         // copy "bar" from initialization_list to vector
Deleting b 00B7FBE8   // delete initialization_list in reverse order. this is "bar"
Deleting b 00B7FAE8   // last to delete. this is "foo"
Deleting b 00B7FAE8  (bug)
// later C::bs is destroyed

您可以在这里看到的是,由于复制的原因,通过push_back初始化向量的速度相当慢。即使你使用了更优雅的方式,这种情况也会发生:

C(initializer_list<B> bb) : bs(bb) {}

一种更快(没有额外副本)的方法是:

C(initializer_list<B> bb) {
    bs.reserve(bb.size());
    bs.insert(bs.end(), bb.begin(), bb.end());
}