Передача 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);

person Baz    schedule 17.08.2012    source источник
comment
Нет, Foo не принимает право собственности. Смысл shared_ptr в том, чтобы разделять право собственности.   -  person juanchopanza    schedule 17.08.2012
comment
Или предпочитаю std::shared_ptr<Stuff> foosStuff(new Stuff());   -  person stefaanv    schedule 17.08.2012
comment
@juanchopanza Возможно, в этом случае вы бы предпочли видеть unique_ptr?   -  person Baz    schedule 17.08.2012
comment
@Baz Если вы действительно хотите стать владельцем, то да.   -  person juanchopanza    schedule 17.08.2012


Ответы (3)


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

shared_ptr ‹> - один из многих стандартных типов библиотек, в которых построение перемещения дешевле, чем построение копии. Создание копии shared_ptr происходит медленнее, поскольку копирование требует, чтобы счетчики ссылок увеличивались атомарно, тогда как перемещение shared_ptr вообще не требует касания ссылочных данных или счетчиков. Из статьи «Хотите скорости? Передайте по значению! "Дэйва Абрахамса можно узнать, что в определенных ситуациях действительно полезно принимать параметр функции по значению. Это один из таких случаев:

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 сделает все за вас всего за одно выделение.

person sellibitze    schedule 17.08.2012
comment
Мне нравится эта функция make_shared - person Baz; 17.08.2012
comment
кстати: если вы перейдете на std :: unique_ptr, вы действительно ИМЕЕТЕ передать его по значению;) - person sellibitze; 17.08.2012
comment
@sellibitze Какая польза от передачи классу shared_ptr в его конструкторе, если вы не собираетесь его оставлять. Конечно, в этом случае лучше передать unique_ptr и позволить классу построить из него свой shared_ptr? - person jleahy; 17.08.2012
comment
@jleahy: Преимущество состоит в том, что конструктор по-прежнему принимает shared_ptr, в то же время не требуя затрат на копирование. Конечно, в этом примере использования я мог бы вместо этого использовать unique_ptr. Но это может без необходимости ограничивать интерфейс. Если вы все равно собираетесь сохранить shared_ptr, просто примите в качестве параметра shared_ptr, а не unique_ptr. Другой вопрос, является ли сохранение shared_ptr хорошей идеей, и на него нельзя просто ответить, не зная, что Baz намеревался здесь делать. - person sellibitze; 17.08.2012
comment
@sellibitze После прочтения статьи, на которую вы ссылались, я теперь понимаю. Если shared_ptr, из которого вы строите, является временным, компилятор сможет полностью исключить копию. - person jleahy; 17.08.2012
comment
@jleahy: Да, современный компилятор это сделает. Но shared_ptr также поддерживает перемещение. Итак, если компилятор по какой-либо причине не может выполнить это копирование, он, по крайней мере, в этом случае прибегнет к конструкции перемещения. :) - person sellibitze; 17.08.2012
comment
Важно уточнить, на какую версию стандарта C ++ вы ссылаетесь, иначе пользователи могут неверно истолковать выводы. Итак, приведенный выше ответ верен, если вы можете его оптимизировать. Стоит проверить выступление Мейерса-Александреску-Саттера, о котором я упоминаю в своем ответе здесь stackoverflow.com/a/8741626/151641 - person mloskot; 08.01.2013

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

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

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

person jleahy    schedule 17.08.2012
comment
Хорошо, мне нужно больше узнать о других интеллектуальных указателях, поскольку я действительно знаком только с shared_ptr. - person Baz; 17.08.2012
comment
Что такое scoped_ptr, это не часть C ++? Может ты имел ввиду std::unique_ptr? А почему именно кастомный делетер? - person Christian Rau; 17.08.2012
comment
@ChristianRau: Я полагаю, что это значит boost::scope_ptr. Бит удаления может быть устаревшей ссылкой на более раннее (скрытое) редактирование? - person Kerrek SB; 17.08.2012
comment
@KerrekSB Я тоже полагаю, но предположения не помогают никому, читающему ответ, понять, что в стандартной библиотеке C ++ есть идеально подходящий указатель на получение права собственности без необходимости использования ускорения (о котором даже не упоминается). И вопрос уже изменился. Как только он заметит изменение вопроса и неточную информацию, голос против его ответа волшебным образом исчезнет. - person Christian Rau; 17.08.2012
comment
@ChristianRau Мой ответ был несколько повторен из-за изменений в вопросе. Если бы не настраиваемое средство удаления, я бы также предпочел make_shared, как в ответе KerrebSB. Я обновил его, чтобы не вводить людей в заблуждение. - person jleahy; 17.08.2012
comment
make_shared - это не просто сохранение (формальной) копии. На самом деле это более эффективная реализация тела общего указателя со стиранием типов. - person Kerrek SB; 17.08.2012

Было бы более эффективно иметь помощника 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.

person Kerrek SB    schedule 17.08.2012