Ложные копии в 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

Remove-std=c++11, затем вы получаете,

Param
Default
Copy
Copy
Assignment

С C++11

без

libc++ пример создания превосходного вывода в режиме c++03

откуда берутся две дополнительные копии?

они связаны с вызовом оператора subscript, не задание. (Они останутся, если вы удалите назначение.) Мне кажется, что они не нужны, даже в мире до c++11, как показывает пример libc++.

Это было первоначально мотивировано глядя на этот вопрос

1 ответов


это LWG 334:

стандарт C++03 предписывает следующие эффекты для operator[] ([lib.карта.access]p1):

возвращает: (*((insert(make_pair(x, T()))).first)).second.


libstdc++ реализует вставку, используемую operator[] (в случае, если ключ еще не существует) в режиме C++03:

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

__i является точкой вставки, она вычисляется как

iterator __i = lower_bound(__k);

__k является параметром operator[].

создание временного 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()));

в сообщении фиксации говорится, что это было

обновление до SGI STL 3.11.


более ранние версии SGI STL (1995) действительно указать map::operator[] таким же образом, как стандарт C++03:

на карте m и клавишу k, m[k] семантически эквивалентно (*((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) from insert(value_type(.. к форме, все еще присутствующей в libstdc++, используя lower_bound и только создание пары, если ключ еще не существует.


таким образом, можно сказать, что libstdc++ реализует первое предлагаемое разрешение LWG 334 (value_type вместо make_pair). Однако, глядя на его историю, это не совсем то, что произошло. Это просто после SGI STL. libc++ строго не соответствует C++03 в этом отношении.


версия C++11 libstdc++того же оператора использует пользовательскую функцию emplacement. Спецификация стандарта C++11 map::operator[] следует предложенной резолюции РГПВ 334:

эффекты: если нет ключа эквивалентно x на карте, вставляет value_type(x, T()) на карте.

(где x - это параметр operator[])