26

Why did C++ adopt free functions for:

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

instead of using static member functions:

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

?

Mankarse
  • 39,818
  • 11
  • 97
  • 141
vladon
  • 8,158
  • 2
  • 47
  • 91
  • 2
    @M.M Thank you, Captain Obvious. The question is: WHY there is no `make` function. – vladon Mar 21 '17 at 09:23
  • 1
    @M.M where I wrote "why you use"? – vladon Mar 21 '17 at 09:26
  • 4
    I just love how three under-caffeinated developers fumbled their answer, myself included. Good morning everyone ;) – Quentin Mar 21 '17 at 09:28
  • @vladon: TBH, I don't think that there is a good reason - that's just how it was proposed. Your proposed syntax is slightly longer and also is different from the existing `make_pair` and `make_tuple` – Vittorio Romeo Mar 21 '17 at 09:30
  • 1
    @VittorioRomeo there is the same for `make_pair` and `make_tuple`: why not `std::pair::make` and `std::tuple::make`? – vladon Mar 21 '17 at 09:32
  • 1
    @vladon because *these* ones deduce the pair/tuple's content from their arguments, and that's their whole rationale -- you can write `std::pair{42, 3.14f}` :) – Quentin Mar 21 '17 at 09:34
  • @vladon: why not `create_pair` or `tuple::build`? It's just how it was proposed and everyone was OK with it. I think you're looking too much into this – Vittorio Romeo Mar 21 '17 at 09:34
  • 6
    Try it. Write a template class that provides a member function, then write code that uses it and **actually compiles** and you'll see why. – Pete Becker Mar 21 '17 at 13:01

3 Answers3

25

TL;DR: Static member functions always have access to private data but free functions only have access to private data when explicitly marked as friend. The choice to implement these functions as free functions (with a small number being implemented as friend functions) isn't a random historical artifact, but is a deliberate decision to improve encapsulation while having a consistent naming scheme for all of the std::make_x functions.


There are many standard factory functions in C++:

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

For all of the above, the make_x function can be implemented correctly using only the public interface of x. In the case of make_shared and make_exception_ptr, the most efficient implementation would require access to the internal data of the std::shared_ptr or std::exception_ptr. All the others can be implemented using just the public interface with zero performance penalty.

Implementing these functions as non-friend free-functions reduces the amount of code that has access to the private internals of the object (a desirable property, since when less code has access to private data, there are fewer places that have to be audited for operations that violate the invariants of the object, and fewer places that potentially need to be changed if the internals of the object change).

If make_shared were the only similar factory function, it might make sense for it to be a member function, but since the majority of such functions are not required to be friend functions to operate efficiently, make_shared is also implemented as a free-function, for the sake of consistency.

This is the correct design, as if static member make functions were consistently used, then in every case apart from make_shared and make_exception_ptr, the member function would unavoidably have excessive access to the private data of the x object. With the standardised design, the small number of make_x functions that need access to private data can be marked as friend, and the rest respect encapsulation correctly by default. If a non-member make_x were used in some cases and a static member make in others, the standard library would become inconsistent and more difficult to learn.

Mankarse
  • 39,818
  • 11
  • 97
  • 141
  • 2
    Pretty sure there are more than 4: http://en.cppreference.com/mwiki/index.php?title=Special%3ASearch&search=make_ – Caleth Mar 21 '17 at 09:57
  • @Caleth: Yes, I am updating the answer now. The expanded list only makes the point clearer. – Mankarse Mar 21 '17 at 10:05
  • 4
    +1: [Effective C++ Item 23: Prefer nonmember nonfriend functions](http://stackoverflow.com/questions/5989734/effective-c-item-23-prefer-non-member-non-friend-functions-to-member-functions). This is just Encapsulation 101. – Matthieu M. Mar 21 '17 at 10:31
17

Consistency.

I don't think that there is any compelling reason to have the ::make syntax instead of the current one. I assume that make_unique and make_shared were preferred to a static ::make function to stay consistent with the existing std::make_pair and std::make_heap functions, that existed pre-C++11.


Note that std::make_pair has a big advantage: it automatically deduces the types of the resultant pair from the function call:

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

If we had std::pair::make, then we would have to write:

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

which defeats the purpose of make_pair.


  • I therefore assume that make_unique and make_shared were chosen because developers were already used to make_pair and similar functions.

  • make_pair was chosen instead of pair::make for the aforementioned benefits.

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 1
    but the question is not about `make_pair` but about `make_unique` which anyway you have to specify the return type both in `unique_ptr::make` and `make_unique` – David Haim Mar 21 '17 at 09:43
  • 8
    @DavidHaim consistency of similar things is a valuable property – Caleth Mar 21 '17 at 09:45
  • @Vittorio Romeo, but you write about the advantage of `make_pair` over `std::pair::make` where it has nothing to with `make_unique` – David Haim Mar 21 '17 at 09:47
  • 4
    @DavidHaim: it has - `make_pair` was chosen in the past over a possible `pair::make` due to argument deduction. `make_unique` was *probably* chosen later over `unique_ptr::make` because `make_pair` already existed. – Vittorio Romeo Mar 21 '17 at 09:52
  • 2
    Also, at least some of the committee prefer non-member non-friend functions over members: http://www.gotw.ca/gotw/084.htm – Caleth Mar 21 '17 at 09:54
  • 2
    @DavidHaim It makes the standard library easier to learn if everything behaves as you expect it to and so given that `std::make_pair` already existed, users of the library come to *expect* creator functions to be stand alone functions beginning with `std::make_...`. – Galik Mar 21 '17 at 10:25
  • -1 There is a compelling reason and it is non-member non-friend functions are increasing encapsulation. Mankarse answer is superior IMHO. – Alessandro Teruzzi Mar 21 '17 at 13:22
3

There is not a concrete reason other than convention alone - a static-class function can do everything a global function can do (functionality wise).
C++ prefers global functions (for utility functions) which contained inside a defined namespace.
Other programming languages (such as Java) prefer static public functions, as global functions are not supported.

this is not new to make_***, other examples exists:

std::this_thread::XXXX instead of std::thread::XXXX_current
although it might had sense to put function which relates to the current thread of execution as static functions inside thread class, they are made global inside this_thread namespace.

also, we could have something like std::container::sort which std::container is a helper class for containers, but we have std::sort instead.

David Haim
  • 25,446
  • 3
  • 44
  • 78