Как лучше использовать emplace с std :: map - PullRequest
2 голосов
/ 08 октября 2019

Я экспериментировал с использованием emplace () вместо std :: map вместо insert. У меня есть простая тестовая программа ниже:

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

class CTest
{
public:
    CTest() : Value(0), Name() { std::cout << "Default constructor" << std::endl; };
    CTest(int val, const std::string &name) : Value(val), Name(name) {
        std::cout << "Parameterized constructor" << std::endl; }
    CTest(const CTest& test) {
        Value = test.Value; Name = test.Name;  std::cout << "Copy Constructor" << std::endl; }
    CTest(CTest&& test) noexcept {
        Value = test.Value; Name = test.Name; std::cout << "Move Constructor" << std::endl; }
    CTest& operator=(const CTest& test) {
        Value = test.Value; Name = test.Name; std::cout << "Copy assignment" << std::endl; return *this; }
    CTest& operator=(CTest &&test) noexcept {
        Value = test.Value; Name = test.Name; std::cout << "Move assignment" << std::endl; return *this; }
    ~CTest() { std::cout << "Destructor" << std::endl; }

private:
    int Value;
    std::string Name;
};

int main()
{
    CTest t1(1, "hello");
    CTest t2(2, "hello");

    std::map<int, CTest> testMap;
    testMap[1] = t1;         //1
    testMap.emplace(2, t2);  //2
    testMap.emplace(3, CTest(3, "hello"));  //3
    testMap.emplace(std::piecewise_construct, std::forward_as_tuple(4), std::forward_as_tuple(4, "hello"));    //4
    testMap.emplace(std::piecewise_construct, std::forward_as_tuple(4), std::forward_as_tuple(std::move(t1))); //5
    return 0;
}

Выход для каждого из них:1Конструктор по умолчаниюКопировать назначение

2Копировать конструктор

3Параметризованный конструктор
Перемещение конструктора
Деструктор

4Параметризованный конструктор

5Перемещение конструктора
Деструктор

1 включает в себя наибольшее количество копий: создайте запись на карте с конструктором по умолчанию, а затем присвойте копию. Я был удивлен, увидев вызов деструктора для 3 и 5. В обоих случаях переданное значение является rvalue. Так создается ли временное значение из переданного значения r, которое удаляется после использования? Возникает вопрос: как правильно использовать emplace? Должны ли вы просто передать аргументы конструктора, как в 4? Это лучший результат, как показывают мои результаты.

1 Ответ

2 голосов
/ 08 октября 2019

Если вы просто передадите аргументы конструктора

Да, потому что это буквально то, для чего предназначены все функции emplace(). С insert() вы должны создать объект, а затем [обычно] скопировать его в свой контейнер. И вообще, если вы используете контейнер, вы только строите, чтобы вы могли поместить их в контейнер. Как вы можете видеть в своих тестах, это немного дополнительная работа.

emplace() был разработан, чтобы позволить вам встроиться непосредственно в контейнер. И вы делаете это, предоставляя параметры конструктора для функции emplace. insert() используется, если у вас уже есть объект и вы хотите поместить его в контейнер.

У меня был странный комментарий, который, как отметили другие, стоит объяснить немного подробнее. Если ваш класс (который я назову Foo) имеет конструкторы с одним параметром, может показаться, что вы можете сделать то же самое, что и emplace(), просто передав один параметр к чему-то вроде insert() или push_back() или любое место, которое будет принимать Foo в качестве параметра. Это «особенность» языка, когда компилятор неявно создаст для вас Foo и будет его использовать. Проблема в том, что под капотом это не то же самое. Где emplace() будет строить ваш объект непосредственно в контейнере, имитация его с помощью конструктора одного параметра все еще заставляет делать копии. Другим недостатком, который следует учитывать, является это неявное преобразование. Это может ухудшить читабельность вашего кода или, что еще хуже, сломать вещи. Этого можно избежать, пометив конструктор как explicit.

...