3

Example:

Foo make_foo(int a1, int a2){
  Foo f(a1,a2);
  return f;
}

Having seen such functions several times, is it just a matter of coding style / preference or is there more to it than meets the eye? Specifically this answer got me thinking with the make_unique implementation and the claim it is exception safe - is that related to the splitting of creation and return? Or am I reading too much into this? Why not simply write

Foo make_foo(int a1, int a2){
  return Foo(a1,a2);
}
Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397

5 Answers5

7

it's perfectly ok to combine creation and return, text books do it on separate lines often to clarify the code somewhat.

johnathan
  • 2,315
  • 13
  • 20
4

It is totally dependent on your requirement and you can put them into single statement.It is done to make the code more readable.

In most of the books and other technical resources there are separate statements for creation ,instantiation and return in the earlier chapters but as you go on reading they are merged into a single statement.

Algorithmist
  • 6,657
  • 7
  • 35
  • 49
4

Note that the answer to which you refer actually has something different:

std::unique_ptr<T> ret (new T(std::forward<Args>(args)...));

In this line of code, explicit dynamic allocation is performed. Best practices dictate that whenever you perform explicit dynamic allocation, you should immediately assign the result to a named smart pointer. For more details, consult the Boost shared_ptr best practices documentation or Herb Sutter's GotW article, "Exception-Safe Function Calls."

It isn't always dangerous to have a new expression as a subexpression of a larger expression, but it's a rule that is easy to forget, so it's best to always follow the best practices guideline and assign a new dynamically allocated object to a named smart pointer.


That said, there is at least one advantage to the pattern of creating a named object and then returning it: it can be a bit easier to "watch" the object in the debugger when stepping through code quickly.

One possible disadvantage is that it is potentially more difficult for a compiler to perform Return Value Optimization (RVO) with a named object. Named Return Value Optimization (NRVO) is not always as straightforward as RVO with an unnamed temporary. I'd hazard to guess that modern compilers wouldn't have a problem either way, but I am not an expert on C++ compiler optimizations.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • But why to a **named** smart pointer? Why not `unique_ptr(new T(...))`? – Xeo Jun 01 '11 at 04:13
  • @James but would not return std::unique_ptr ret(new T(std::forward(args)...)); work? – johnathan Jun 01 '11 at 04:15
  • @johnathon: It would, but the idea is that if you _consistently_ follow the pattern of assigning ownership of dynamically allocated objects to named smart pointers, you are far less likely to accidentally make a mistake and end up with non-exception-safe code. Patterns are good, especially when it comes to code in which errors are frequent, as is the case in code that deals with object lifetimes and ownership. – James McNellis Jun 01 '11 at 04:18
  • @Xeo, @johnathon: The problem with exception safety mentioned by James is that if I had something like: `fooFunc(new A(), g())`, then if the first `new A()` is executed and `g()` throws, I have a leak. Since it is easy to miss such a detail (especially when adding an argument, or changing a function that did not throw for a function which throws), the pattern James talks about is strict to prevent mistakes. – Matthieu M. Jun 01 '11 at 06:24
  • Or, more perniciously, consider the case with default arguments, e.g. `void f(A*, B* = g());` called as `f(new A())` where `g()` throws. At the call site, it _looks_ okay but in fact is quite broken. @Matthieu – James McNellis Jun 01 '11 at 06:39
  • The issue is to avoid more than one allocation/smart pointer creation in a single statement. That's all. This is not a reason not to put the statement in the return. – James Kanze Jun 01 '11 at 08:23
  • @James: That isn't entirely accurate: in the example in my previous comment, there is only one allocation. Yes, you are correct: in many cases, it is perfectly safe to use `new` in a context other than initializing or assigning to a named smart pointer. It's difficult, though, to quickly identify where it is safe and where it is not, and it's easy to forget when you absolutely need a named smart pointer, so the best course of action is to follow the best practice and always use a named smart pointer. The cost is low (a little more typing) the benefit is great (consistency and correctness). – James McNellis Jun 01 '11 at 15:36
  • @James McNellis As long as there is only one resource being allocated and used to create an RAII class per statement, there is no problem. I don't see how that is hard to analyze or identify. Returning a smart pointer with an immediately `new`ed object is a more or less standard idiom. – James Kanze Jun 01 '11 at 16:30
0

Personally, any time I write a class-template I also write a corresponding make_ function-template to allow for creation of objects of the class-template's type without explicitly specifying the template arguments. Of course, this is generally only when using a compiler with C++0x's auto semantics or when passing a temporary as a function argument, but in my opinion both of these are common enough scenarios to warrant the effort.

That said, I've never written code like this for creation of a non-template type.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
0

Historically, the first version

Foo make_foo(int a1, int a2)
{
    Foo f(a1,a2);
    return f; 
} 

had a better chance of triggering the NRVO optimization in some compilers (like older MSVC).

Version 2 should work just as well for compilers implementing the RVO, but historically it wasn't as reliable.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • I was of the impression that RVO of an unnamed temporary was far more likely than NRVO: the compiler knows that the object is being constructed there and then and it knows that if it will be copied to wherever the result needs to be, so it's always possible to elide that copy (ignoring exceptions, which can hinder any kind of RVO). Why do you say that NRVO is more likely to occur than non-named RVO? – James McNellis Jun 01 '11 at 15:51
  • @James - I'm saying that it used to be the case for earlier versions of MSVC. Creating a named object and returning it triggered more optimizations than returning a temporary. This has obviously affected some code bases and recommendations. – Bo Persson Jun 01 '11 at 16:04