【问题标题】:Double delete in initializer_list vs 2013initializer_list 与 2013 中的双重删除
【发布时间】:2013-11-23 16:57:54
【问题描述】:

今天在我的项目中遇到了内存问题,其中一个类使用了 c++ 11 initializer_list。系统发出内存问题的信号:“dbgdel.cpp 中的表达式 _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(删除两次!)

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

【问题讨论】:

  • C 是什么,Bs 有什么作用?
  • 对不起。我在答案中加入了 C。
  • A 与示例相关吗?
  • 不,这是简化示例的遗留问题。
  • 在析构函数中设置断点并比较堆栈跟踪以查看它发生的位置。

标签: c++ visual-c++ c++11 visual-studio-2013 initializer-list


【解决方案1】:

initializer_list 行为有问题。在其析构函数中,它调用整个范围的向量删除(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());
}

【讨论】:

  • C(const initializer_list&lt;B&gt;&amp; bb) : bs(bb.begin(), bb.end()) {} 怎么样?
  • 感谢您的出色回答。我已经怀疑一个编译器错误,这对我来说很不满意。您是否碰巧知道一种解决方法,或者我应该避免以这种方式使用 intializer_list ?我也对向量笨拙的调整大小行为感到惊讶,它是否总是增加一个元素并在每次推回时复制所有元素?我认为区分容量和大小是有原因的。
  • 同样,向量不知道大小并从 1 开始。然后增加 50% 或请求的新大小。因此,一一相加会导致向量大小为 1、2、3、4、6、9、11、...
  • 是的,SO 中的另一篇文章报告了 initializer_list 中的泄漏。看起来您可以将它用于普通数据类型或最少的 C 数据类型。太糟糕了,看起来是个不错的功能:(
猜你喜欢
  • 1970-01-01
  • 2016-12-23
  • 1970-01-01
  • 2012-11-12
  • 1970-01-01
  • 1970-01-01
  • 2011-04-01
  • 1970-01-01
  • 2011-05-14
相关资源
最近更新 更多