Эффективный способ создания объекта со строковым членом - PullRequest
0 голосов
/ 26 апреля 2019

Предположим, у меня есть класс ThisHasAStringMember. Предположим, у него есть закрытый член, который является строкой, и я хочу эффективно получить строковое значение, предпочитая перемещение по копии, где это возможно. Достигнут ли это следующие два конструктора?

class ThisHasAStringMember
{
public:
  // ctors
  ThisHasAStringMember(const std::string str) : m_str(str) {}
  ThisHasAStringMember(std::string &&str) : m_str(std::move(str)) {}

  // getter (no setter)
  std::string value() { return m_str; }
private:
  std::string m_str;
}

Нужен ли двойной амперсанд перед параметром str во втором конструкторе?

Это правильный способ сделать это?

1 Ответ

2 голосов
/ 27 апреля 2019

Сначала я заметил бы, что лучше пометить ваши конструкторы как явные.

Следующим моментом будет то, что лучше изменить первый конструктор в вашем решении, чтобы он использовал ссылку на констант, чтобы избежать копирования lvalue:

// ctors
ThisHasAStringMember(const std::string& str) : m_str(str) {}
ThisHasAStringMember(std::string &&str) : m_str(std::move(str)) {}

Этот подход является оптимальным с точки зрения производительности (у вас будет один вызов конструктора копирования для lvalue и один вызов конструктора перемещения для rvalue), однако в каждом случае довольно скучно реализовывать каждый раз два конструктора.А если у вас N членов - 2 ^ N конструкторов.

Есть несколько альтернатив:

  1. Конструктор Signle, в который вы передаете параметр только по значению.Да, это было неэффективно в C ++ 98, но в C ++ 11, когда вы создаете полную копию - это опция.

    ThisHasAStringMember(std::string str) : m_str(std::move(str)) {}

Когда lvalueбудет передан один вызов конструктора копирования и один вызов конструктора перемещения.Когда будет передано значение rvalue, будет два вызова конструктора перемещения.Да, у вас есть один дополнительный вызов конструктора перемещения в каждом из случаев.Но это часто очень дешево (или даже может быть оптимизировано компилятором), а код очень прост.

Один конструктор, в котором вы передаете параметр по rvalue:

ThisHasAStringMember(std::string&& str) : m_str(std::move(str)) {}

Если вы передаете lvalue, вы должны явно скопировать его первым в местевызова, например ThisHasAStringMember (copy (someStringVar)).(здесь copy - простой метод копирования шаблонов).И у вас все еще будет один дополнительный вызов конструктора перемещения для lvalues.Для значений не будет накладных расходов.Лично мне нравится такой подход: все места, где копируется параметр, являются явными, вы не будете делать случайные копии в местах, критичных к производительности.

Создайте шаблон конструктора и используйте совершенную пересылку:
template <typename String, 
    std::enable_if_t<std::is_constructible_v<std::string, String>>* = nullptr>
ThisHasAStringMember(String&& str) : m_str(std::forward<String>(str))
{}

У вас не будет никаких накладных расходов как для значений r, так и для значений lvalue, но вам нужно будет создать шаблон конструктора и определить его в заголовке вбольшинство случаев.

...