Reputation: 2573
Before C++11, I would write my functions like so:
struct Color {
float R,G,B,A;
Color(float NewR,float NewG,float NewB,float NewA):
R(NewR),G(NewG),B(NewB),A(NewA)
};
struct Item {
Color color;
void SetColor(const Color& new_color)
{
color=new_color; // Copy is created
}
};
And then call it like so:
a_item.SetColor(Color(1.0f,1.0f,1.0f,1.0f));
Color new_color(1.0f,0.0f,0.0f,1.0f); // alternative way
a_item.SetColor(new_color);
Now with C++11, I write my functions like this when applicable:
struct Item {
Color color;
void SetColor(Color new_color)
{
color=std::move(new_color); // No extra copy is created
}
};
And then call it like so:
a_item.SetColor(Color(1.0f,1.0f,1.0f,1.0f)); // already Rvalue
Color new_color(1.0f,0.0f,0.0f,1.0f); // alternative way
a_item.SetColor(std::move(new_color));
I was wondering if this was a good practice. It seems that when an object ownership is transferred, my functions become pass-by-value with move semantics.
Speaking of const correctness, should my constructor be defined like this:
Color(const float NewR,const float NewG,const float NewB,const float NewA):
R(NewR),G(NewG),B(NewB),A(NewA)
And my SetColor function defined like this?:
void SetColor(const Color new_color)
{
color=std::move(new_color); // No extra copy is created
}
Upvotes: 1
Views: 412
Reputation: 42554
I'll throw the "perfect forwarding setter" technique into the mix:
struct Item {
Color color;
template <typename T>
void SetColor(T&& new_color)
{
color=std::forward<T>(new_color);
}
};
This will move from rvalues, copy from lvalues, and even accepts other types that are convertible to Color
.
Upvotes: 1
Reputation: 56863
Regarding the first part, this seems basically fine, but read to the end of this answer. For the second part, you should first know that top-level const
is stripped from the function's signature. So this
void foo( Color c );
is identical to
void foo( const Color c );
For the caller, it therefore makes no difference.
The only difference is when you define the function, if you put const
to a parameter for the definition, the compiler won't modify it. This doesn't make sense in the context you are asking the question: If you want to move the instance somewhere else, it is not const
.
If you think const
is the right way to go, use a const reference. Now back to your first question: Using void f( Color c );
seems easy and in some cases, superfluous copies are avoided. The problem is that this does not work in all cases and on todays compilers. Compared to overloading const Color&
and Color&&
, sometimes an additional move is generated. The only benefit is that you need fewer overloads, which might become important once you have multiple parameters.
To explain the difference with code:
void X::f( Color c )
{
this->c = std::move(c); // 1 move=
}
void X::g( const Color& c )
{
this->c = c; // 1 copy=
}
void X::g( Color&& c )
{
this->c = std::move(c); // 1 move=
}
X x;
Color c;
x.f(c); // 1 copy-ctor, 1 move=
x.g(c); // 1 copy=
x.f(Color()); // 1 ctor, 1 move=
x.g(Color()); // 1 ctor, 1 move=
x.f(std::move(c)); // 1 move-ctor, 1 move=
x.g(std::move(c)); // 1 move=
As you can see, the "traditional" way of using a const reference combined with a C++11 rvalue-reference overload has advantages wrt the number of move-operations (which still do have a cost). Balance it with the simplification of not having overloads and judge for yourself.
Upvotes: 4