c++03 libstdc++ 与 c++11 中的虚假副本

Spurious copies in c++03 libstdc++ vs c++11

考虑这段代码:

#include <iostream>
#include <string>
#include <map>

using namespace std;

class Foo
{
public:
   Foo() : _x(0) 
   {
       cout << "Default" << endl;
   }
   Foo(int a) : _x(a)
   {
      cout << "Param" << endl;
   }

   Foo(Foo const &foo) :
      _x(foo._x)
   {
      cout << "Copy" << endl;
   }

   Foo& operator=(Foo const &foo)
   {
      cout << "Assignment" << endl;
      _x = foo._x;
      return *this;
   }

   int get(void)
   {
      return _x;
   }

private:
   int _x;
};

int main(int argc, char *argv [])
{
   std::map<int, Foo> foos;

   Foo a_foo(10);

   foos[100] = a_foo;

   return 0;
}

在 gcc 中使用 -std=c++11 编译,你得到输出,

Param
Default
Assignment

删除-std=c++11,然后你得到,

Param
Default
Copy
Copy
Assignment

with c++11

without

libc++ example producing the superior output in c++03 mode

多出来的两份是哪里来的?

它们与调用下标运算符有关,与赋值无关。 (如果您删除分配,它们仍然存在。)对我来说,它们似乎并不需要,即使在 C++11 之前的世界中,如 libc++ 示例所示。

最初的动机是看 this question

这是LWG 334:

C++03 标准为 operator[] ([lib.map.access]p1) 规定了以下效果:

Returns: (*((insert(make_pair(x, T()))).first)).second.


libstdc++在C++03模式下实现operator[]使用的插入(在key不存在的情况下)如下:

 __i = insert(__i, value_type(__k, mapped_type()));

__i为插入点,计算为

iterator __i = lower_bound(__k);

__koperator[]的参数。

创建临时 value_type(__k, mapped_type()) 会导致第一个副本(从 mapped_type()value_type 对)。第二个副本是 insert 的结果,它将 value_type 对复制到实际节点中。

1997 年的原始版本是:

return (*((insert(value_type(k, T()))).first)).second;

这几乎符合标准(当时甚至不存在!)。上一次发生重大变化是在 1998 年。在此之前,它使用:

__i = insert(__i, value_type(__k, _Tp()));

提交消息说这是为了

Update to SGI STL 3.11.


Earlier versions of the SGI STL (1995) 确实以与 C++03 标准相同的方式指定了 map::operator[]

For a map m and key k, m[k] is semantically equivalent to (*((m.insert(make_pair(k, T()))).first)).second .

SGI STL v2.03 (1997) 已经改用 value_type 而不是 make_pair。正如 gcc 的提交日志所暗示的那样,SGI STL 的实现在 v3.0(也是 1997 年)和 v3.11(1998 年)之间再次发生变化,从 insert(value_type(.. 到 libstdc++ 中仍然存在的形式,使用 lower_bound 并且只创建如果密钥尚不存在,则配对。


所以可以说 libstdc++ 实现了 LWG 334 的第一个建议决议(value_type 而不是 make_pair)。然而,回顾它的历史,事实并非如此。它只是跟随SGI STL。 libc++ 在这方面并不严格符合 C++03。


libstdc++ 的同一运算符的 C++11 版本使用自定义放置函数。 C++11 标准的 map::operator[] 规范遵循 LWG 334 的建议决议:

Effects: If there is no key equivalent to x in the map, inserts value_type(x, T()) into the map.

(其中xoperator[]的参数)