与 VS2013 相比,gcc 4.7.2 中的 std::map 实现效率非常低?
Posted
技术标签:
【中文标题】与 VS2013 相比,gcc 4.7.2 中的 std::map 实现效率非常低?【英文标题】:std::map implementation very inefficient in gcc 4.7.2 compared to VS2013? 【发布时间】:2013-11-04 10:31:45 【问题描述】:有人可以帮我理解 std::map 容器是如何实现的吗?我有一个包含原子成员的类,我不需要调用复制构造函数,因此我使用 c++11 删除运算符来抑制复制构造函数的隐式生成。
MyCalss(const MyClass& a) = delete;
这在我的 windows 版本中运行良好,但是在 Linux 中我收到一个错误,通知我 std::map 类的 [] 运算符正在尝试调用已删除的函数。
Windows VS2013 和 Linux GCC 4.7.x 的 map 实现之间似乎存在重大差异。这让我做了一个关于如何将对象插入地图的实验。
我写了这个小例子程序:
#include <stdlib.h>
#include <stdio.h>
#include <map>
#include <iostream>
#include <string>
using namespace std;
class TestItem
public:
TestItem ()
_name = "TestItem" + id();
cout << "Constructing " << _name << endl;
TestItem (const TestItem & other)
_name = "TestItem " + id();
cout << "Copying " << other._name << " to new " << _name <<endl;
string id()
static int id = 0;
char buf[2];
sprintf_s(buf, "%d", id++);
return string(buf);
~TestItem()
cout << "Destroying " << _name << endl;
void doStuff()
// stub
string _name;
;
void run()
cout << "making new obj" << endl;
TestItem a;
cout << endl << endl;
map<string, TestItem> TestItemMap;
cout << "Makeing new obj as part of a map insert" << endl;
TestItemMap["foo"].doStuff();
cout << endl << endl;
cout << "adding a value to the map" << endl;
TestItemMap["new foo key"] = a;
cout << endl << endl;
cout << "looking up a value that has already been inserted" << endl;
TestItem& b = TestItemMap["foo"];
cout << endl << endl;
int main(int argc, char** argv)
run();
在 Windows 中,当我运行这个程序时,我得到以下输出:
making new obj
Constructing TestItem0
Making new obj as part of a map insert
Constructing TestItem1
adding a value to the map
Constructing TestItem2
looking up a value that has already been inserted
Destroying TestItem1
Destroying TestItem0
Destroying TestItem0
这是我在内部编写时希望看到的
TestItemMap["foo"].doStuff();
我希望地图会创建一个新的 TestItem 实例,然后通过将树节点内部链接到新的 TestItem 将其插入 RedBlack 树。
但是,当我在 Linux 中运行相同的代码时,结果却大不相同
making new obj
Constructing TestItem0
Making new obj as part of a map insert
Constructing TestItem1
Copying TestItem1 to new TestItem2
Copying TestItem2 to new TestItem3
Destroying TestItem2
Destroying TestItem1
adding a value to the map
Constructing TestItem4
Copying TestItem4 to new TestItem5
Copying TestItem5 to new TestItem6
Destroying TestItem5
Destroying TestItem4
looking up a value that has already been inserted
Destroying TestItem0
Destroying TestItem3
Destroying TestItem0
这将向我表明 [] 运算符正在创建 TestItem 的新实例,然后调用外部 map.insert() 函数,然后销毁新创建的 TestItem,这仅解释了对复制构造函数的调用之一. gcc中的c++ stdlib真的这么低效吗?
人们使用一些标准技巧来解决这个问题吗?
【问题讨论】:
GCC默认使用的标准库叫做libstdc++,是开源的。也附带 GCC 源代码。如果您想知道它是如何实现的,只需下载源代码并查看即可。 您正在比较启用优化的两个编译,不是吗? 至于你的问题,我会更好奇说明TestItem0
被破坏的两行行。
sprintf_s 不是标准的,所以它不能在 linux 上编译。你用了什么代码?
@JoachimPileborg 这是用于 TestItemMap["new mooky key"] = a;
的显然未考虑的赋值运算符的结果,我敢打赌 OP 没有看到它的到来。
【参考方案1】:
首先,我修复了那个可怕的sprintf_s
东西:
string id()
static int id = 0;
std::stringstream s;
s << id++;
return s.str();
并且还更改了您的“查找已插入的值”以实际执行它所说的[编辑:你也是:-)]
现在,在 C++03 模式下使用 g++ 4.8.1 进行编译,我得到的结果与您的相似。但是用-std=c++11
编译,我得到了
making new obj
Constructing TestItem0
Making new obj as part of a map insert
Constructing TestItem1
adding a value to the map
Constructing TestItem2
looking up a value that has already been inserted
Destroying TestItem0
Destroying TestItem1
Destroying TestItem0
似乎 MSVC 会自动使用 C++11 特性(最有可能是移动语义)来提供很好的性能提升,而您需要明确告诉 g++ 也这样做。
【讨论】:
【参考方案2】:这似乎是 GCC 4.8 修复的错误。
Here 它适用于 GCC 4.8 Here GCC 4.7 编译失败【讨论】:
我敢说这不是 GCC 错误,而是 MSVC 无法正常工作。存在用户声明的复制构造函数时,不会生成隐式声明的移动构造函数。因此,GCC 不应该因为不能使用移动语义而受到责备——用户已经告诉它不要这样做。 (不要问我为什么 4.8 版“有效”) @Damon 新元素默认就地构造,然后赋值,我实际上看不到元素在哪里/应该被复制。错误 AFAICT 是 GCC 4.7 在实例化operator[]
时添加新元素时在内部需要复制可构造性,这是标准不要求/不允许的。
@Damon 我认为它很可能是 std::map
内部的一个内部 Node
结构(或类似的东西)正在被移动构造,其效果是避免了用户的复制构造函数.
@TristanBrindle 不,不需要复制或移动构造函数,节点应使用默认构造函数并在适当位置构造新元素。这就是 VC 和 GCC 4.8 正在做的事情。然后,他们从 operator[]
返回对这个新元素的引用,并进行分配,请参阅我链接的示例。以上是关于与 VS2013 相比,gcc 4.7.2 中的 std::map 实现效率非常低?的主要内容,如果未能解决你的问题,请参考以下文章
LinuxC++开发面试系列:代码运行gcc编译system函数与VS2013 C4996解决方法
LinuxC++开发面试系列:代码运行gcc编译system函数与VS2013 C4996解决方法
LinuxC++开发面试系列:代码运行gcc编译system函数与VS2013 C4996解决方法