2017-01-04 15 views
2

Конкретно: direct-list-initialization (cppreference.com(3)).Есть ли причина, по которой std :: make_shared/std :: make_unique не использует инициализацию списка?

Оба std::make_shared и равномерной инициализации функции были введены в C++. Таким образом, мы можем использовать инициализацию агрегата при распределении объектов в куче: new Foo{1, "2", 3.0f}. Это хороший способ непосредственно инициализировать объекты, которые не имеют конструкторов, такие как агрегаты, стручки и т.д.

А реальных сценарии, такие как декларирование случайных структур внутри функции, эффективно поставлять набор аргументов лямбда стал очень распространенным явлением, в моем опыте:

void foo() 
{ 
    struct LambdaArgs 
    { 
     std::string arg1; 
     std::string arg2; 
     std::string arg3; 
    }; 

    auto args = std::make_shared<LambdaArgs>(LambdaArgs{"1", "2", "3"}); 

    auto lambda = [args] { 
     /// ... 
    }; 

    /// Use lambda 
    /// ... 
} 

Здесь auto args = std::make_shared<LambdaArgs>("1", "2", "3"); whould быть хорошо, но это не будет работать, потому что std::make_shared обычно реализуется как:

template<typename T, typename... Args> 
std::shared_ptr<T> make_shared(Args && ...args) 
{ 
    return std::shared_ptr<T>(new T(std::forward<Args>(args)...)); 
} 

Итак, мы застряли с auto args = std::make_shared<LambdaArgs>(LambdaArgs{"1", "2", "3"});.

Проблема, которая должна была быть решена с помощью std::make_shared, по-прежнему сохраняется для объекта без конструктора. И обходной путь не только неэстетичен, но и менее эффективен.

Это другой надзор или есть некоторые причины, которые защищают этот выбор. В частности, какие подводные камни могут быть в решении инициализации списка? std::make_unique был введен позже, в C++ 14, почему он тоже следует тому же шаблону?

+1

Не ответ на ваш вопрос, но обходной путь: если вы используете кортеж вместо случайной структуры, вам будет предоставлен соответствующий конструктор, и это даже терьер: 'using LambdaArgs = std :: tuple '; – Frank

+0

Проблема 'std :: make_shared' заключается в том, чтобы убедиться, что все очищено правильно, если исключение выбрано после создания объекта, но до того, как shared_ptr переходит в собственность. С учетом сказанного это выглядит как незначительный надзор. – Kevin

+0

Вы можете найти это интересно: http://stackoverflow.com/questions/24234480/stdmake-shared-with-stdinitializer-list – marcinj

ответ

3

В частности, какие подводные камни могут быть в решении инициализации списка?

Все типичные ошибки при использовании инициализации списка.

Например, скрытие конструкторов non-initializer_list. Что делает make_shared<vector<int>>(5, 2)? Если ваш ответ «строит массив из 5 int s», это абсолютно правильно ... до тех пор, пока make_sharedне использует инициализацию списка. Потому что это меняет момент, когда вы это делаете.

Обратите внимание, что внезапное изменение этого будет нарушить существующий код, так как прямо сейчас все функции косвенной инициализации используют синтаксис конструктора. Поэтому вы не можете просто изменить его волей-неволей и ожидать, что мир будет продолжать работать.

Плюс один более уникальным для этого случая: Сужение вопрос:

struct Agg 
{ 
    char c; 
    int i; 
}; 

Вы можете сделать Agg a{5, 1020}; инициализировать этот агрегат. Но вы никогда не сможете сделать make_shared<Agg>(5, 1020). Зачем? Поскольку компилятор может гарантировать, что литерал 5 может быть преобразован в char без потери данных. Однако, когда вы используете косвенную инициализацию, как это, буква 5 выставляется шаблоном как int. И компилятор не может гарантировать, что любые int могут быть преобразованы в char без потери данных. Это называется «сужающимся преобразованием» и явно запрещено в инициализации списка.

Вам необходимо будет явно преобразовать это 5 в char.

В стандартной библиотеке есть проблема: LWG 2089. Хотя технически этот вопрос говорит о allocator::construct, он должен в равной мере применяться ко всем непрямым функциям инициализации, таким как make_X и конструкторы на месте C++ 17 для any/optional/variant.

Почему он тоже придерживается той же картины?

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

+0

Кадровая структура BTW не является заменой * aggregate *, это имя, которое я придумал для структур, объявленных для одноразового использования в узком контексте (* body функции *). – GreenScape

1

Проблема, которая должна была быть решена с помощью std :: make_shared, по-прежнему сохраняется для объекта без конструктора.

Нет, проблема не сохраняется. Основная проблема make_shared - это решение проблемы утечки памяти между объектом и владением смарт-указателем. Он также способен удалить одно дополнительное выделение для блока управления.

Да, неудобно не использовать прямую инициализацию, но это никогда не было объявленной целью make_shared.

+1

make_shared не имеет ничего общего с утечками памяти. Что обеспечивает make_shared, гарантируется локальность ссылки между счетчиком ссылок и объектом, что потенциально позволяет избежать лишнего промаха в кеше при разыменовании. – Frank

+1

@Frank, возможно, вы можете воспитывать себя, глядя здесь: http://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared – SergeyA

+1

Я стою исправлено. Спасибо за ссылку! Изменить: ваша ссылка действительно говорит о том, что исключение дополнительного распределения является основной причиной, и избежать утечки памяти является хорошим преимуществом, я не чувствую, что это глупо. – Frank