перемещать или копировать при передаче аргументов в конструктор и функции-члены - PullRequest
0 голосов
/ 27 июня 2018

Ниже приведен пример моего типичного кода. Есть много объектов, которые выглядят так:

struct Config
{
    Config();
    Config(const std::string& cType, const std::string& nType); //additional variables omitted
    Config(Config&&) = default;
    Config& operator=(Config&&) = default;

    bool operator==(const Config& c) const;
    bool operator!=(const Config& c) const;

    void doSomething(const std::string& str);
    bool doAnotherThing(const MyOtherObject& obj);
    void doYetAnotherThing(int value1, unsigned long value2, const std::string& value3, MyEnums::Seasons value4, const std::vector<MySecondObject>& value5);

    std::string m_controllerType;
    std::string m_networkType;
    //...
};

//...

Config::Config(const std::string& cType, const std::string& nType) :
    m_controllerType(cType),
    m_networkType(nType)
{
}

Мои мотивы и общее понимание предмета:

  • использовать константные ссылки в конструкторах и методах, чтобы избежать двойного копирования при передаче объектов.
  • простых типов - передача по значению; классы и структуры - проходите по константной ссылке (или простой ссылке, когда мне нужно изменить их)
  • заставляет компилятор создавать default конструктор перемещения и задание перемещения, чтобы он мог выполнять свою причудливую магию и одновременно позволять избежать написания скучных ctor() : m_v1(std::move(v1)), m_v2(std::move(v2)), m_v3(std::move(v3)) {}.
  • если он работает плохо, используйте libc и raw-указатели, затем оберните его в классе и напишите комментарий.

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

После прочтения cppreference, Скотта Майерса, стандарта C ++, Страуструпа и т. Д. Я чувствую: «Да, я понимаю каждое слово здесь, но оно по-прежнему не имеет никакого смысла». Единственное, что я, король, понял, это эта семантика перемещения имеет смысл, когда мой класс содержит не копируемые типы, такие как std::mutex и std::unique_ptr.

Я видел много кода, где люди передают сложный объект по значению, например, большие строки, векторы и пользовательские классы - я верю, что именно здесь происходит семантика перемещения, но, опять же, как вы можете передать объект в функцию двигаться? Если я прав, он оставил бы объект в «своего рода нулевом состоянии», что сделало бы его непригодным для использования.

Итак, вопросы: Правильно выбрать между передачей по значению и передачей по ссылке? - Нужно ли предоставлять конструкторы копирования и перемещения? - Нужно ли явно писать перемещение и копировать конструкторы? Могу ли я использовать = default? Мои классы в основном являются объектами POD, поэтому здесь нет сложных входов в систему. - При отладке я всегда могу написать std::cout << "move\n"; или std::cout << "copy\n"; в конструкторах своих классов, но как мне узнать, что происходит с классами из stdlib?

P.S. Может показаться, что это крик отчаяния (это так), а не действительный вопрос SO. Я просто не знаю, чтобы сформулировать свои проблемы лучше, чем это.

1 Ответ

0 голосов
/ 27 июня 2018
  • Если это тип примитива, передайте по значению. Населенный пункт побеждает.

  • Если вы не собираетесь хранить его копию, передайте по значению или const&.

  • Если вы хотите сохранить ее копию, а переместить ее очень дешево и скромно дорого, передайте значение .

  • Если что-то требует умеренных затрат на перемещение и является параметром приемника, рассмотрите возможность передачи по ссылке rvalue. Пользователи будут вынуждены std::move.

  • Рассмотрите возможность предоставления вызывающим сторонам возможности встроить конструкцию в поле в очень универсальном коде или там, где вам нужна каждая унция производительности

Правило 0/3/5 описывает, как вы должны обрабатывать копирование назначить / построить / уничтожить. В идеале вы следуете правилу 0; копирование / перемещение / уничтожение - это все =default во всем, кроме типов управления ресурсами Если вы хотите реализовать любое копирования / перемещения / уничтожения, вам нужно внедрить =default или =delete каждого другого из 5.

Если вы используете только один аргумент для установщика, рассмотрите возможность написания как &&, так и const& версий установщика. Или просто разоблачение основного объекта. Назначение перемещения иногда использует хранилище, и это эффективно.

Уплотнение выглядит так:

struct emplace_tag {};
struct wrap_foo {
  template<class...Ts>
  wrap_foo(emplace_tag, Ts&&...ts):
    foo( std::forward<Ts>(ts)... )
  {}
  template<class T0, class...Ts>
  wrap_foo(emplace_tag, std::initializer_list<T0> il, Ts&&...ts):
    foo( il, std::forward<Ts>(ts)... )
  {}
private:
  Foo foo;
};

Существует множество других способов, которыми вы можете разрешить конструкцию "emplace". См. Также emplace_back или emplace в стандартных контейнерах (где они используют размещение ::new для конструирования объектов, пересылки переданных объектов).

Конструкция Emplace позволяет даже прямое построение даже без движения, используя объекты с правильно настроенной operator T(). Но это то, что выходит за рамки этого вопроса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...