views:

992

answers:

8

Should a C++ (implicit or explicit) value constructor accept its parameter(s) by value or reference-to-const, when it needs to store a copy of the argument(s) in its object either way?

Here is the shortest example I can think of:

struct foo {
    bar _b;
    foo(bar [const&] b) // pass by value or reference-to-const?
        : _b(b) { }
};

The idea here is that I want to minimize the calls to bar's copy constructor when a foo object is created, in any of the various ways in which a foo object might get created.

Please note that I do know a little bit about copy elision and (Named) Return Value Optimization, and I have read "Want Speed? Pass by Value", however I don't think the article directly addresses this use case.

Edit: I should be more specific.

Assume that I can't know the sizeof(bar), or whether or not bar is a fundamental, built-in type (bar may be a template parameter, and foo may be a class template instead of a class). Also, don't assume that foo's constructor can be inlined (or bar's, for that matter). Do assume that I at least might be using a compiler that implements RVO.

What I would like is for there to be a possibility (given compiler optimizations) that a call like this will invoke no calls to bar's copy constructor whatsoever (even when executing _b(b) in foo's initialization list):

foo f = function_that_creates_and_returns_a_bar_object_using_rvo();

Is there any possibility (given the C++98 standard) that this can be done, and if so, is it more or less likely to work if foo accepts its parameter by reference-to-const instead of by value?

A: 

Hold a shared_pointer to bar in your class and pass it as such. That way you never call the copy constructor :).

Patrick
The shared_pointer's copy constructor will be called.
tstenner
Which is very light weight... worrying about that would be a case of premature optimisation if ever I heard of one. I was just trying to provide a different approach :)
Patrick
+4  A: 

Look this question.

Use const T & arg if sizeof(T)>sizeof(void*) and use T arg if sizeof(T) <= sizeof(void*). All fundamental types should be the exception to this rule

Alexey Malistov
Although I think this is a good rule of thumb, it seems to conflict with the guideline presented by the referenced article (at least sometimes)...And in my specific use case, foo is a class template with bar as a template parameter, so I do not know the sizeof(bar) (perhaps I should have included that in my example :( ).
Kurt Glastetter
Use type traits to choose reference or value depending of sizeof(bar)
Alexey Malistov
@Alexey: except for functors and iterators (according to Meyers) which should always be passed by value and we then hope that the compiler does the right thing. Not sure what Meyers has smoked to get to this conclusion, though.
Konrad Rudolph
Kurt Glastetter
+1  A: 

I'm assuming bar has a normal copy constructor of the form bar(const bar &b)

Take a const reference here. bar's constructor will then take that reference, and perform the copy. Total copies: 1.

If you left the reference off, the compiler would make a copy of b, pass the copy to foo's constructor, which would then pass it to bar and copy that.

Thanatos
+5  A: 

All things being equal, I pass by const& for sufficiently complex classes, and value for POD and simple objects.

Laying out the pros/cons to pass by const reference instead of traditional pass by value

Positives:

  • Avoids a copy (big plus for objects with an expensive copy)
  • Read-only access

Negatives:

  • Someone can const cast the const off the reference if they really wanted to

More importantly with the positives is you explicitely control when the copy happens (in your case when passing initializing _b in your initializer list). Thinking about the negatives... I agree it is a risk. I think almost all good programmers will feel dirty about emplying the const_cast. Moreover you can dutifully search for const_cast and throw tomatoes at the person casting the const off the argument. But hey you never know and who has time to watch code like a hawk :)?

My subjective opinion is that in sufficiently complex classes and in environments where the performance matters the benefit of avoiding the copy constructor outweighs the risk. However for really dumb and POD classes, I tend to make copies of the data and pass by value.

Doug T.
A: 

Honestly, for this case, the best option is making your constructor inline, provided bar's constructor does not throw. Then just pass by reference.

Also, as you suspected, your article does not apply here.

rlbond
+2  A: 

Stylistically, I'd say that passing by reference is the better way.

If the performance really matters, then don't guess. Measure it.

Kristopher Johnson
+1 for recommending measuring performance instead of guessing.
Void
+1  A: 

I haven't checked what the standard says, but by trying this empirically I can tell you that GCC does not optimize the copy away, regardless of whether the constructor accepts the argument by value or by const reference.

If, however, the constructor takes a const reference, it manages to avoid an unnecessary copy when you create foo from an existing bar object.

To summarize:

Bar b = makeBar();         // No copy, because of RVO
FooByValue f1 = makeBar(); // Copy constructor of Bar called once
FooByRef f2 = makeBar();   // Copy constructor of Bar called once
FooByValue f3(b);          // Copy constructor of Bar called twice
FooByRef f4(b);            // Copy constructor of Bar called only once

Not that I am a compiler expert, but I guess it makes sense that it generally can't RVO a return value into an arbitrary place (such as the member field of the foo object). Instead, it requires the target to be on the top of the stack.

Josef Grahn
+1  A: 

In C++98 and C++03, you should pass const& bar and then copy. In C++0x, you should pass bar and then do a move (provided bar has a move constructor).

#include <utility>

struct foo
{
    bar _b;

    foo(bar b) : _b(std::move(b)) {}
};

If you construct foo with an lvalue parameter, the copy constructor will be called to create a copy b, and that copy will be moved into _b. If you construct foo with an rvalue parameter, bar's move constructor will be called to move into b, and then it will be moved again into _b.

Fred
I'm considering this the answer because it is the most future-proof, and it shows how this is a deficiency in C++98/03 that will be resolved by C++0x. However, see Alexey Malistov's answer (and following comments) for cases where you might want to pass by value for C++98/03 as well.
Kurt Glastetter