Почему лучше использовать std:: make_ * вместо конструктора?
В STL есть некоторые функции, начинающиеся с префикса make_
, например std::make_pair
, std::make_shared
, std::make_unique
и т.д. Почему лучше использовать их вместо простого использования конструктора?
auto pair2 = std::pair< int, double >( 1, 2.0 );
auto pair3 = std::make_pair( 1, 2.0 );
std::shared_ptr< int > pointer1 = std::shared_ptr< int >( new int( 10 ) );
std::shared_ptr< int > pointer2 = std::make_shared< int >( 10 );
- Я просто вижу, что эти функции делают код немного короче, но это все?
- Есть ли другие преимущества?
- Эти функции безопаснее использовать?
Ответы
Ответ 1
Помимо преимущества включения аргументации (как уже упоминалось в других ответах), есть и другие преимущества.
std::make_pair<T1, T2>
не просто возвращает std::pair<T1, T2>
. Если вы передадите значение с помощью std::ref
, то возвращенная пара не сохранит std::reference_wrapper
, она сохранит ссылку.
std::make_shared
может комбинировать распределения. shared_ptr
требуется место для хранения таких вещей, как refcount, список слабых указателей и т.д., которые не могут быть сохранены непосредственно в shared_ptr
. Они могут быть объединены с создаваемым объектом в одном чуть большем блоке, а не в двух отдельных блоках.
std::make_shared
и std::make_unique
оба удостоверяются, что никакой объект не останется позади, если будут сброшены исключения. Если вы вызываете функцию как f(std::shared_ptr<int>(new int), std::shared_ptr<int>(new int))
, тогда возможно, что компилятор сначала выделит два объекта int
, а затем построит два объекта shared_ptr<int>
. Если второе распределение выходит из строя, и объект shared_ptr<int>
еще не настроен, чтобы освободить память при уничтожении, тогда у вас есть утечка памяти. std::make_shared
и std::make_unique
объединить выделение int
и конструкцию std::shared_ptr<int>
в вызове функции, а другое выделение int
и другую конструкцию std::shared_ptr<int>
в другом вызове функции. Вызовы функций не могут пересекаться, поэтому, если второе выделение не удастся, есть уже общий указатель, который будет уничтожен, также отменив первое выделение.
Ответ 2
Несмотря на то, что это может быть субъективным, основное преимущество этого метода заключается в следующем:
писать код против интерфейсов, а не реализации
В сущности, экземпляр шаблона функции выполняет вывод типа на основе аргументов, которые вы передаете, тогда как экземпляр класса не работает. Как следствие, вам не придется передавать шаблонные аргументы так же, как при непосредственном создании класса.
Следует отметить, однако, что речь идет не о "сохранении нескольких символов", а о том, чтобы сделать ваш код более общим и избегать привязки к конкретному типу в вызове функции.
Однако это не всегда так, как показал ваш пример std::make_shared
, все еще есть случаи, когда вам нужно передать этот тип в качестве аргумента шаблона. Но, поскольку указывает Herb Sutter, есть еще несколько преимуществ, когда дело доходит до использования std::make_shared
:
-
Сначала вы должны написать для ясности и правильности, а std::make_shared
достигает обоих из них (субъективный, но я согласен)
-
с помощью std::make_shared
более эффективен, поскольку он выделяет ваш объект, а также объект shared_ptr
за один раз, что позволяет снизить накладные расходы на распределение и, вероятно, улучшить выравнивание кеша.
Ответ 3
make_unique
скрывает от вас "сырой" указатель, что обычно хорошо - это меньше подвержено ошибкам.
make_shared
может улучшить распределение памяти для экземпляров shared_ptr<X>
. Обычно, когда вы используете конструктор shared_ptr<X>
, он будет выделять память дважды, сначала, например, X
, а второй - внутренние данные (например, счетчик ссылок). make_shared
позволяет оптимизировать - он будет создавать единую внутреннюю структуру, содержащую как X
, так и счетчик ссылок, поэтому он будет выполнять выделение одной памяти. И так же, как прежде, он скрывает исходные указатели.