Правильно ли использовать std :: map :: emplace с функцией, возвращающей shared_ptr? - PullRequest
0 голосов
/ 10 октября 2018

Если у меня есть какая-то функция, которая возвращает std::shared_ptr<T>, как мне вставить результат вызова этой функции в std::map<U, std::shared_ptr<T>>: с insert и make_pair или просто emplace?

Допустим, я уже знаю, что дублирующего ключа нет, и что я однопоточный.

std::shared_ptr<Foo> bar() {
   return std::make_shared<Foo>();
}

std::map<std::string, std::shared_ptr<Foo>> my_map;

// Which makes more sense?
my_map.emplace("key", bar());
// or
my_map.insert(make_pair("key", bar()));

Работает ли RVO с std::map::emplace?

1 Ответ

0 голосов
/ 11 октября 2018

Я думаю, что это правильно.


RVO

Я думаю, что RVO должно работать нормально, если оно реализовано в вашем компиляторе , потому что bar() возвращает временный объект, и компилятор знает, какой объект должен быть возвращен из bar().Кроме того, в C ++ 17 и более в этом случае гарантируется RVO.

Применимо ли RVO (оптимизация возвращаемого значения) ко всем объектам?

Что такое оптимизация копирования и возврата значения?


emplace vs. insert ( DEMO )

std :: map ::emplace

In

my_map.emplace("key", bar());

, RVO заменяет bar() на std::make_shared<Foo>() и создается std::shared_ptr<Foo>.Затем он пересылается и move-ctor из std::shared_ptr<Foo> называется только один раз при построении на месте нового элемента my_map.

std :: map :: insert с std :: make_pair

В

my_map.insert(make_pair("key", bar()));

, RVO снова заменяет bar() на std::make_shared<Foo>().

Далеес 20.3.2 из N3337 , C ++ 11 с исправленными незначительными ошибками, шаблон класса std::pair равен

namespace std{
  template<class T1, class T2>
  struct pair
  {
    typedef T1 first_type;
    typedef T2 second_type;
    T1 first;
    T2 second;
    ...
    template<class U, class V> pair(U&& x, V&& y);
    ...
  }
}

, где шаблон ctor равен

template<class U, class V> pair(U&& x, V&& y);

Эффекты : конструктор инициализирует first с std::forward<U>(x) и second с std::forward<V>(y).

Кроме того, с 20.3.3

template<class T1, class T2>
pair<V1, V2> make_pair(T1&& x, T2&& y);

Возвращает : pair<V1, V2>(std::forward<T1>(x), std::forward<T2>(y)); ...

Таким образом, типичная реализация std::make_pair для этого определения рассматривается так:

namespace std{
  template<class T1, class T2>
  inline pair<typename decay<T1>::type, typename decay<T2>::type>
  make_pair(T1&& x, T2&& y)
  {
    return pair<typename decay<T1>::type,
                typename decay<T2>::type>
                (std::forward<T1>(x), std::forward<T2>(y));
  }
}

и RVO снова будет работать для std::make_pair.Фактически, число вызовов move-ctor увеличивается на единицу, если мы скомпилируем DEMO с C ++ 14 и опцией компилятора "-fno-elide-constructors", которая отключает RVO для g ++.

Поэтому в std::make_pair("key", bar()) я ожидаю следующее senario:

  • 1) RVO заменяет bar() на std::make_shared<Foo>(),
  • 2) RVO также заменяет std::make_pairс std::pair::pair,
  • 3) std::shared_prtr<Foo> создается и затем пересылается на std::pair::pair,
  • 4) std::shared_prtr<Foo> создается как элемент second с помощью move-ctor .

Наконец, std::map::insert также перегружен для значений r:

// since C++11
template< class P >
std::pair<iterator,bool> insert( P&& value );

Здесь снова

  • 5) move-ctor из std::shared_prtr<Foo> вызывается.

В итоге я ожидаю, что

my_map.insert(make_pair("key", bar()));

вызывает move-ctor из std::shared_ptr<Foo> дважды .

Мой ответ

Поскольку в обоих случаях копии std::shared_ptr<Foo> не делаются вообще и почти одинаковое количество ходовназываются, их производительность будет почти такой же.

Перемещение объектаt в карту

вставка vs emplace vs оператор [] в карту c ++

Является ли оператор c ++ 11 [] эквивалентным emplace onвставка карты?


Примечание 1, универсальная ссылка

В C ++ 11 и более поздних, левый и правый аргумент std::make_pair - это то, что Скотт Мейерс называет универсальная ссылка , то есть они могут принимать и l-значение, и r-значение:

// until C++11
template< class T1, class T2 >
std::pair<T1,T2> make_pair( T1 t, T2 u );

// since C++11
template< class T1, class T2 >
std::pair<V1,V2> make_pair( T1&& t, T2&& u );

Таким образом, если мы передаем l-значение std::make_pair, то copy-ctor std::shared_ptr<Foo> вызывается в результате универсальной ссылки.


Примечание 2, std :: map :: try_emplace

std::map::emplace всегда создает новый элемент my_map, даже еслиключ уже существует.Если ключ уже существует, эти новые экземпляры уничтожаются.Но начиная с C ++ 17, мы получаем

template <class... Args>
pair<iterator, bool> try_emplace(key_type&& k, Args&&... args);

Эта функция не создает args, если ключ k уже существует в контейнере.Хотя мы уже знаем, что в данной проблеме нет повторяющегося ключа, если мы не знаем, существует ли дублирующий ключ, этот std::map::try_emplace предпочтительнее.

Есть лиесть ли причина использовать std :: map :: emplace () вместо try_emplace () в C ++ 1z?

...