如何优化 std::map insert() 函数?
Posted
技术标签:
【中文标题】如何优化 std::map insert() 函数?【英文标题】:How to optimize std::map insert() function? 【发布时间】:2017-06-20 18:46:12 【问题描述】:解释我正在尝试完成的最佳方法是使用此示例(使用Visual Studio 2008 SP1
编译):
struct ELEMENT1
//Its members
ELEMENT1()
//Constructor code
~ELEMENT1()
//Destructor code
;
std::map<std::wstring, ELEMENT1> map;
std::pair<std::map<std::wstring, ELEMENT1>::iterator, bool> resIns;
ELEMENT1 element;
std::wstring strKey;
for(size_t i = 0; i < numberRepetitions; i++)
//Do processing
//...
//set 'strKey'
//Insert new element into the map first
resIns = map.insert(std::pair<std::wstring, ELEMENT1>(strKey, element)); //This line calls ELEMENT1 constructor & destructor twice
//Then fill out the data
fill_in_data(resIns.first->second);
BOOL fill_in_data(ELEMENT1& outInfo)
//Fill in 'outInfo' -- MUST be in its own function
//...
我的目标是优化这段代码,因此我做了以下工作:
将 ELEMENT1 element
构造/破坏移到循环之外。
我将element
插入map
,然后尝试使用指向插入元素的指针填充它,而不是构造新元素,然后填充它,然后将其复制到地图中,然后将其销毁。 (至少这是计划。)
但是当我为 Release
构建编译它并检查汇编代码时,我可以看到带有 map.insert()
函数的 C++ 行调用了 ELEMENT1
构造函数两次!然后是其析构函数的两倍。因此,以下机器代码仅适用于 map.insert()
行:
所以我显然没有在这里看到任何东西。
有人可以建议编译后的代码中发生了什么以及是否可以对其进行优化?
【问题讨论】:
你能升级编译器吗?如果可以,那么您可以升级到 C++11 并使用为解决此问题而构建的emplace
。
@NathanOliver:不,现在升级编译器是不可能的。也许以后。
您的 std::pair 类型与 map::value_type 不匹配...
我是未来,您能否将屏幕截图中的信息以纯文本或代码格式的文本形式发布?
@MichaelBurr:我试过了。而且它看起来并不像在那个屏幕截图中那么好。 SO 格式化引擎完全毁掉了它。
【参考方案1】:
您有 2 个构造函数调用的原因是您传递给 insert
的内容与它需要的不匹配。 std::map::insert
需要 const value_type&
和 value_type
的地图是
std::pair<const key_type, element_type>
^^^^^ this is important
因此,由于它们不匹配,因此您在使用时构造了一个元素
std::pair<std::wstring, ELEMENT1>(strKey, element)
然后编译器调用复制构造函数将其转换为
std::pair<const std::wstring, ELEMENT1>
快速解决方法是将代码更改为
std::pair<const std::wstring, ELEMENT1>(strKey, element)
这给你留下了一个临时的,它是被构造和破坏的。您也可以按照zett42 在their answer 中的建议进行操作,以完全避免创建临时文件。
【讨论】:
谢谢,它确实照顾了一个构造函数。但是还剩下一个,在insert()
返回后立即有一个析构函数。您的代码和ELEMENT1& resIns = map[ strKey ];
都这样做——后者在模板内部。有没有办法只调用赋值运算符?
@c00000fd 在构造对象之前,您不能将其分配给对象,这意味着您将至少有一个构造函数调用。使用ELEMENT1& resIns = map[ strKey ];
,您应该只有一个构造函数调用。
是的,没错。我只接到一个电话,还有你的方法。
快速跟进。调用构造函数来添加每个元素是std::map
特有的吗?因为std::vector
没有这个要求。
@c00000fd 不,这不是唯一的,向量中的每个元素也是构造的。【参考方案2】:
resIns = map.insert(std::pair<std::wstring, ELEMENT1>(strKey, element));
您正在构建一个临时的std::pair
,其成员second
是ELEMENT1
。这会导致调用ELEMENT1
的复制构造函数。
第二次调用ELEMENT1
的复制构造函数是std::map::insert()
在映射中创建一个新元素,该元素将由临时std::pair
初始化。
您可以通过使用std::map::operator[]
来避免临时导致的重复构造函数调用:
ELEMENT1& resIns = map[ strKey ];
fill_in_data( resIns );
如果strKey
不存在于地图中,ELEMENT1
将直接在地图中默认构造,并返回对新对象的引用。构造函数只会被调用一次。
如果地图中已经存在strKey
,则将返回对现有对象的引用。
【讨论】:
这确实是一个优雅的解决方案,它摆脱了一个构造函数......但仍然存在一个。有没有办法让它使用赋值运算符?【参考方案3】:您应该使用 emplace 来避免在临时对象上创建:
resIns = map.emplace
(
::std::piecewise_construct
, ::std::forward_as_tuple(strKey)
, ::std::forward_as_tuple()
);
切换到更新的 VS 版本的好理由。
【讨论】:
VS2008 不支持 C++11/emaplce
不是我,OP。我有一个问题要问他是否可以升级。
这里使用piecewise_construct
有什么好处? emplace(strKey, element)
不够吗?
@FrançoisAndrieux 如果没有piecewise_construct
,我们将无法使用map.emplace(strKey);
调用值默认构造函数来放置类似的东西。以上是关于如何优化 std::map insert() 函数?的主要内容,如果未能解决你的问题,请参考以下文章