Сравнение производительности: f (std :: string &&) против f (T &&) - PullRequest
0 голосов
/ 29 мая 2018

Я пытаюсь понять влияние на производительность использования WidgetURef::setName (URef - универсальный эталон, термин, придуманный Скоттом Мейерсом) против WidgedRRef::setName (RRef - эталон R-значения):

#include <string>

class WidgetURef {
    public:
    template<typename T>
    void setName(T&& newName)
    {
        name = std::move(newName);
    }
    private:
        std::string name;
};

class WidgetRRef {
    public:
    void setName(std::string&& newName)
    {
        name = std::move(newName);
    }
    private:
        std::string name;
};

int main() {
    WidgetURef w_uref;
    w_uref.setName("Adela Novak");

    WidgetRRef w_rref;
    w_rref.setName("Adela Novak");
}

Я ценю, что с универсальными ссылками следует вместо этого использовать std::forward, но это всего лишь (несовершенный) пример для выделения интересного бита.

Вопрос В этом конкретном примере, что влияет на производительность при использовании одной реализации против другой?Хотя WidgetURef требует вывода типа, в остальном он идентичен WidgetRRef, не так ли?По крайней мере, в этом конкретном сценарии в обоих случаях аргумент является ссылкой r-value, поэтому временные файлы не создаются.Правильно ли это рассуждение?

Контекст Пример взят из статьи 25 Скотта Мейерса «Эффективный современный C ++» (стр. 170).Согласно книге (при условии, что мое понимание верно!), Версия, использующая универсальную ссылку T&&, не требует временных объектов, а другая, принимающая std::string&&, не требует.Я действительно не понимаю, почему.

Ответы [ 4 ]

0 голосов
/ 29 мая 2018

Принимая аргументы, как указано в вопросе

template<typename T>
void setName(T&& newName)
{
    name = std::forward<T>(newName);
}

Вызовет оператор присваивания std::string для элемента данных name с аргументом const char *

void setName(std::string&& newName)
{
    name = std::move(newName);
}

Вызывает конструктор std::string для создания временного объекта, к которому может быть привязана ссылка Rvalue.

Вызывает std::string перемещение назначения / конструктора для элемента данных name с аргументом std::string&&

Вызывает std::string деструктор для уничтожения временного объекта, из которого мы переместили данные.

0 голосов
/ 29 мая 2018

setName(T&& newName) с аргументом "Adela Novak" получает значение T как const char (&)[12], которое затем присваивается std::string.

setName(std::string&& newName) с аргументом "Adela Novak" создает временный std::string объект, который затем перемещается, назначенный на std::string.

Первый здесь более эффективен, потому что в нем нет перемещения.

0 голосов
/ 29 мая 2018

Сам Скотт говорит, что WidgetURef "компилируется, но плохо, плохо, плохо! " (дословно).Эти два класса ведут себя по-разному, так как вы используете std::move вместо std::forward: setName, поэтому можете изменять свой аргумент:

#include <string>
#include <iostream>

class WidgetURef {
    public:
    template<typename T>
    void setName(T&& newName)
    {
        name = std::move(newName);
    }
    private:
        std::string name;
};

int main() {
    WidgetURef w_uref;
    std::string name = "Hello";
    w_uref.setName(name);
    std::cout << "name=" << name << "\n";
}

может легко печатать name=, что означает, что значение nameбыл изменен.И действительно, в идеальном случае это происходит по крайней мере.

С другой стороны, WidgetRRef требует , чтобы переданный аргумент был ссылкой на rvalue, поэтому приведенный выше пример не скомпилируетсябез явного setName(std::move(name)).

Ни WidgetURef, ни WidgetRRef не требуют создания дополнительных копий, если вы передаете std::string в качестве аргумента.Однако, если вы передадите что-то, из чего может быть назначено std::string (например, const char*), то первый пример передаст это по ссылке и назначит его в строку (без каких-либо копий, за исключением копирования данных из строки в стиле C вstd::string), а во втором примере сначала создается временный string, а затем передается его как ссылка на метод.Эти свойства сохраняются, если вы замените std::move(newName) на правильный std::forward<T>(newName).

0 голосов
/ 29 мая 2018

В этом конкретном примере, каковы последствия для производительности при использовании одной реализации по сравнению с другой?

Универсальные ссылки, как их называет Скотт Мейерс, в первую очередь существуют не по соображениям производительности,но, грубо говоря, обрабатывать обе ссылки L- и Rvalue одинаково, чтобы избежать бесчисленных перегрузок (и для возможности распространения всей информации о типах во время пересылки).

[...] такВременные работы не создаются.Правильно ли это обоснование?

Rvalue-ссылки не препятствуют созданию временных файлов.Rvalue-ссылки - это те ссылки, которые могут быть привязаны к временным файлам (кроме ссылок на const lvalue)!Конечно, в вашем примере будут временные файлы, но ссылка на значение может быть привязана к нему.Универсальная ссылка сначала должна подвергнуться свертыванию ссылки, но в конце поведение в вашем случае будет идентичным:

// explicitly created temporary
w_uref.setName(std::string("Adela Novak")); 
// will create temporary of std::string --> uref collapses to rvalue ref
// so is effectively the same as
w_rref.setName("Adela Novak");

Используя ссылку rvalue с другой стороны, вы принудительно назначаете временное значение как std::string&& не может привязаться к этому литералу.

w_rref.setName("Adela Novak"); // need conversion

Таким образом, компилятор создаст временный std::string из литерала, на который ссылается rvalue, а затем может привязаться к нему.

Я не знаюна самом деле не понятно, почему.

В этом случае шаблон будет преобразован в const char(&)[12], и, таким образом, временное std::string не будет создано в отличие от случая выше.Следовательно, это более эффективно.

...