Действительно ли make shared более эффективен, чем new?
я экспериментировал с shared_ptr
и make_shared
из C++11 и запрограммировал маленький игрушечный пример, чтобы увидеть, что на самом деле происходит при вызове make_shared
. В качестве инфраструктуры я использовал llvm / clang 3.0 вместе с библиотекой llvm std c++ в XCode4.
class Object
{
public:
Object(const string& str)
{
cout << "Constructor " << str << endl;
}
Object()
{
cout << "Default constructor" << endl;
}
~Object()
{
cout << "Destructor" << endl;
}
Object(const Object& rhs)
{
cout << "Copy constructor..." << endl;
}
};
void make_shared_example()
{
cout << "Create smart_ptr using make_shared..." << endl;
auto ptr_res1 = make_shared<Object>("make_shared");
cout << "Create smart_ptr using make_shared: done." << endl;
cout << "Create smart_ptr using new..." << endl;
shared_ptr<Object> ptr_res2(new Object("new"));
cout << "Create smart_ptr using new: done." << endl;
}
теперь взгляните на выход, пожалуйста:
создать smart_ptr с помощью make_shared...
make_shared в конструктор
конструктор копирования...
конструктор копирования...
деструктор
деструктор
создать smart_ptr с помощью make_shared: готово.
создать smart_ptr с помощью new...
конструктор new
создать smart_ptr с помощью new: done.
деструктор
деструктор
получается, что make_shared
вызывает конструктор копирования в два раза. Если я выделяю память для Object
через обычный new
этого не происходит, только один есть.
мне интересно следующее. Я слышал это make_shared
должен быть более эффективным, чем использование new
(1, 2). Одна из причин-потому что make_shared
выделяет счетчик ссылок вместе с объектом можно управлять в том же блоке памяти. Ладно,я понял. Это, конечно, более эффективно, чем два отдельные операции распределения.
напротив, я не понимаю, почему это должно идти со стоимостью двух вызовов конструктора копирования Object
. Из-за этого я не уверен, что make_shared
более эффективно, чем распределение с помощью new
на каждый случае. Я ошибаюсь? Ну хорошо, можно реализовать конструктор перемещения для Object
но все же я не уверен, что это более эффективно, чем просто выделение Object
через new
. По крайней мере не во всех случаях. Это было бы верно, если копирование Object
дешевле, чем выделение памяти для счетчика ссылок. Но ... --1--> - внутренний счетчик ссылок может быть реализован с использованием нескольких примитивных типов данных, верно?
можете ли вы помочь и объяснить, почему make_shared
это путь с точки зрения эффективности, несмотря на описанные накладные расходы на копирование?
4 ответов
в качестве инфраструктуры я использовал llvm / clang 3.0 вместе с библиотекой llvm std c++ в XCode4.
Ну, это, кажется, ваша проблема. Стандарт C++11 устанавливает следующие требования к make_shared<T>
(и allocate_shared<T>
), в разделе 20.7.2.2.6:
требуется: выражение:: new(pv) T(std::forward (args)...), где pv имеет тип void* и указывает на хранилище, пригодное для хранения объекта типа T, должно быть хорошо сформировано. A быть распределителем (17.6.3.5). Конструктор копирования и деструктор не должен бросать исключений.
T
is не требуется, чтобы быть copy-constructable. Действительно,T
даже не требуется, чтобы быть не-размещение-новый конструктивный. Требуется только быть конструктивным на месте. Это означает, что только make_shared<T>
могу сделать с T
is new
это на месте.
таким образом, результаты, которые вы получаете, не соответствуют стандарту. Инфраструктура LLVM это libc++ сломан в этом отношении. Подать отчет об ошибке.
для справки, вот что произошло, когда я взял ваш код в VC2010:
Create smart_ptr using make_shared...
Constructor make_shared
Create smart_ptr using make_shared: done.
Create smart_ptr using new...
Constructor new
Create smart_ptr using new: done.
Destructor
Destructor
Я также портировал его на оригинал Boost shared_ptr
и make_shared
, и я получил то же самое, что и VC2010.
Я бы предложил подать отчет об ошибке, так как поведение libc++нарушено.
вы должны сравнить эти две версии:
std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));
в вашем коде вторая переменная - это просто голый указатель, а не общий указатель вообще.
теперь о мясе. make_shared
и (на практике) более эффективно, потому что он выделяет опорный блок управления вместе с фактическим объектом в одном динамическом распределении. Напротив, конструктор для shared_ptr
который принимает голый указатель объекта должен выделить другое динамическая переменная для счетчика ссылок. Компромисс в том, что make_shared
(или его двоюродный брат allocate_shared
) не позволяет указать пользовательский делетер, так как распределение выполняется распределителем.
(это не влияет на конструкцию самого объекта. От Object
С точки зрения нет никакой разницы между двумя версиями. Более эффективным является сам общий указатель, а не управляемый объект.)
так что одна вещь, чтобы иметь в виду ваши настройки оптимизации. Измерение производительности, особенно в отношении c++, - это бессмысленно без включенной оптимизацией. Я не знаю, действительно ли вы компилировали с оптимизациями, поэтому я подумал, что это стоит упомянуть.
это сказало, что вы измеряете с этим тестом не так что make_shared
более эффективно. Проще говоря, вы измеряете неправильно :-П.
здесь сделка. Обычно при создании общего указателя он имеет по крайней мере 2 элемента данных (возможно, больше). Один для указателя, и один для отсчета ссылок. Этот счетчик ссылок выделяется в куче (чтобы его можно было разделить между shared_ptr
С разных жизней...в этом-то все и дело!)
так что если вы создаете объект с чем-то вроде std::shared_ptr<Object> p2(new Object("foo"));
есть по крайней мере 2 звонки new
. Для Object
и одно для отсчета ссылки объект.
make_shared
имеет опцию (я не уверен, что она должна), чтобы сделать один new
который достаточно большой, чтобы удерживать объект, на который указывают, и счетчик ссылок в одном и том же смежном блоке. Эффективно выделяя объект, который выглядит примерно так (иллюстративный, а не буквально, что это такое).
struct T {
int reference_count;
Object object;
};
поскольку счетчик ссылок и время жизни объекта связаны друг с другом (не имеет смысла жить дольше, чем другой). Весь этот блок может быть delete
d в то же время.
таким образом, эффективность заключается в распределении, а не в копировании (что, как я подозреваю, связано с оптимизацией больше всего).
чтобы быть ясным, это то, что boost должен сказать о make_shared
http://www.boost.org/doc/libs/1_43_0/libs/smart_ptr/make_shared.html
кроме удобства и стиля, такая функция также сейф исключения и значительно быстрее, потому что он может использовать одно распределение для и объект и свой соответствуя блок управления, исключая a значительная часть строительных накладных расходов shared_ptr. Этот устраняет одну из основных жалоб на эффективность shared_ptr.
вы не должны получать никаких дополнительных копий там. Вывод должен быть:
Create smart_ptr using make_shared...
Constructor make_shared
Create smart_ptr using make_shared: done.
Create smart_ptr using new...
Constructor new
Create smart_ptr using new: done.
Destructor
Я не знаю, почему вы получаете дополнительные копии. (хотя я вижу, что вы получаете один "деструктор" слишком много, поэтому код, который вы использовали для вывода, должен отличаться от кода, который вы опубликовали)
make_shared
является более эффективным, потому что он может быть реализован с использованием только одного динамического выделения вместо двух, и потому, что он нуждается в памяти одного указателя меньше учета на общий объект.
Edit: я не проверял с Xcode 4.2, но с Xcode 4.3 я получаю правильный вывод, который я показываю выше, а не неправильный вывод, показанный в вопросе.