Подтвердить что ты не робот

Почему std:: make_unique вместо std:: unique_ptr:: make?

Почему С++ использует бесплатные функции для:

std::make_unique(...);
std::make_shared(...);

вместо использования статических функций-членов:

std::unique_ptr::make(...); // static
std::shared_ptr::make(...); // static

?

4b9b3361

Ответ 1

TL; DR: функции статического члена всегда имеют доступ к конфиденциальным данным, но свободные функции имеют доступ только к закрытым данным, когда они явно помечены как friend. Выбор для реализации этих функций как свободных функций (с небольшим числом, реализуемым как функции друзей) не является случайным историческим артефактом, а является преднамеренным решением об улучшении инкапсуляции, имея последовательную схему именования для всех функций std::make_x.


В С++ есть многие стандартные функции factory:

std::make_pair
std::make_tuple
std::make_unique
std::make_shared //efficiency
std::make_exception_ptr //efficiency
std::make_move_iterator
std::make_reverse_iterator
std::make_error_code
std::make_error_condition
//And several more are proposed for C++17

Для всех вышеперечисленных функция make_x может быть реализована правильно, используя только открытый интерфейс x. В случае make_shared и make_exception_ptr для наиболее эффективной реализации потребуется доступ к внутренним данным std::shared_ptr или std::exception_ptr. Все остальные могут быть реализованы с использованием только открытого интерфейса с нулевым штрафом за производительность.

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

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

Это правильный дизайн, как если бы были последовательно использованы функции static member make, то в каждом случае, кроме make_shared и make_exception_ptr, функция-член неизбежно имела бы чрезмерный доступ к личным данным x объект. При стандартизованном дизайне небольшое число функций make_x, которые нуждаются в доступе к личным данным, может быть помечено как friend, а остальное - по умолчанию. Если в некоторых случаях использовался нечлен make_x и статический член make в других, стандартная библиотека стала бы непоследовательной и трудной для изучения.

Ответ 2

Согласованность.

Я не думаю, что есть какая-то веская причина иметь синтаксис ::make вместо текущего. Я полагаю, что make_unique и make_shared были предпочтительнее статической функции ::make, чтобы оставаться совместимой с существующими функциями std::make_pair и std::make_heap, которые существовали до С++ 11.


Обратите внимание, что std::make_pair имеет большое преимущество: он автоматически выводит типы результирующей пары из вызова функции:

auto p0 = std::make_pair(1, 1.f); // pair<int, float>

Если бы у нас было std::pair::make, нам пришлось бы написать:

auto p1 = std::pair<int, float>::make(1, 1.f);

который побеждает цель make_pair.


  • Поэтому я предполагаю, что make_unique и make_shared были выбраны, потому что разработчики уже использовали make_pair и подобные функции.

  • make_pair был выбран вместо pair::make для вышеупомянутых преимуществ.

Ответ 3

Нет конкретной причины, кроме конвенции, функция статического класса может делать все, что может сделать глобальная функция (функциональность мудрая).
С++ предпочитает глобальные функции (для служебных функций), которые содержатся внутри определенного пространства имен.
Другие языки программирования (например, Java) предпочитают статические публичные функции, поскольку глобальные функции не поддерживаются.

это не ново для make_***, существуют другие примеры:

std::this_thread::XXXX вместо std::thread::XXXX_current
хотя может быть смысл поставить функцию, относящуюся к текущему потоку выполнения как статические функции внутри класса thread, они становятся глобальными внутри пространства имен this_thread.

мы могли бы иметь что-то вроде std::container::sort, которое std::container является вспомогательным классом для контейнеров, но вместо этого мы имеем std::sort.