Передача std:: shared_ptr в конструкторы

Является ли следующий разумный и эффективный подход в отношении создания материала и предоставления ему права Foo?

class Foo
{
    explicit Foo(const std::shared_ptr<Stuff>& myStuff)
        : m_myStuff(myStuff)
    {
    }

    ...

private:
    const std::shared_ptr<Stuff> m_myStuff;
}

std::shared_ptr<Stuff> foosStuff(new Stuff());
Foo f(foosStuff);

Ответ 1

Поскольку вы заинтересованы в эффективности, я бы хотел сделать два момента:

shared_ptr < > является одним из многих стандартных типов библиотек, где конструкция перемещения дешевле, чем конструкция копии. Копирование построения shared_ptr происходит медленнее, так как копирование требует, чтобы счетчики ссылок увеличивались атомарно, тогда как перемещение shared_ptr не требует касания ссылочных данных или счетчиков вообще. Из статьи "" Want Speed? Pass by value!" Дейва Абрахама можно узнать, что в определенных ситуациях полезно воспользоваться параметром функции по значению. Это один из следующих случаев:

class Foo
{
  explicit Foo(std::shared_ptr<Stuff> myStuff)
  : m_myStuff(move(myStuff))
  {}

  ...

private:
  std::shared_ptr<Stuff> m_myStuff;
};

Теперь вы можете написать

Foo f (std::make_shared<Stuff>());

где аргумент является временным, а shared_ptr не копируется (просто перемещается один или два раза).

Использование std:: make_shared здесь имеет то преимущество, что сделано только одно выделение. В вашем случае вы сами выделили объект Stuff, а конструктор shared_ptr должен был распределить опорные счетчики и динамически делить. make_shared делает все это для вас только с одним выделением.

Ответ 2

Да, это совершенно разумно. Таким образом, работа, связанная с управлением общим указателем, должна выполняться только один раз, а не дважды, если вы передали его по значению. Вы также можете рассмотреть возможность использования make_shared, чтобы избежать создания копии.

std::shared_ptr<Stuff> foosStuff(std::make_shared<Stuff>());

Единственное улучшение, которое вы могли бы сделать, - это если Foo является единственным владельцем (т.е. вы не собираетесь поддерживать foosStuff после создания Foo), тогда вы можете переключиться на использование std:: unique_ptr или boost:: scoped_ptr (который является эквивалентом pre-С++ 11), который будет иметь меньшие накладные расходы.

Ответ 3

Может быть более эффективным иметь помощник make_foo:

Foo make_foo() { return Foo(std::make_shared<Stuff>()); }

Теперь вы можете сказать auto f = make_foo();. Или, по крайней мере, используйте вызов make_shared самостоятельно, так как полученный shared_ptr может быть более эффективным, чем результат, построенный из выражения new. И если Stuff фактически принимает аргументы конструктора, может быть подходящим частный вспомогательный конструктор:

struct Foo
{
    template <typename ...Args>
    static Foo make(Args &&... args)
    {
        return Foo(direct_construct(), std::forward<Args>(args)...);
    };

private:

    struct direct_construct{};

    template <typeaname ...Args>
    Foo(direct_construct, Args &&... args)
    : m_myStuff(std::make_shared<Stuff>(std::forward<Args>(args)...))  // #1
    {  }
};

Вы можете либо обернуть Foo::make в вышеуказанный make_foo, либо использовать его напрямую:

auto f = Foo::make(true, 'x', Blue);

Тем не менее, если вы действительно не разделяете права собственности, std::unique_ptr<Stuff> звучит как более предпочтительный подход: он концептуально намного проще, а также несколько более эффективен. В этом случае вы скажете m_myStuff(new Stuff(std::forward<Args>(args)...)) в строке с надписью #1.