Reading this answer regarding returning rvalue references from function got me thinking, how can I write an id
funct开发者_运维问答ion in C++0x.
Basically, I want id
to be a do nothing function, a function that has no observable effects on the program.
My first attempt is the following:
#include <iostream>
class X
{
public:
X(std::string&& s) : s(std::move(s)) {};
X(const std::string& s) : s(s) {};
std::string s;
~X() { std::cout << "Destroying: " << s << std::endl; }
private:
X(const X&) {};
X(X&&) {};
};
template <class T>
T&& id(T&& x) { return static_cast<T&&>(x); }
int main()
{
auto&& x1 = X("x1");
std::cout << "Line 1" << std::endl;
auto&& x2 = id(X("x2"));
std::cout << "Line 2" << std::endl;
}
However, I fear that in this case, x2 is a dangling reference, as X("x2")
is destroyed before "Line 2" executes.
So here, quite clearly id
has an observable effect.
How can I write an id
function in C++0x, which in particular works for types without move/copy constructors.
You can't. As a rule, you shouldn't write functions that return rvalue references- and as you pointed out correctly, you cannot extend the lifetime of the temporary long enough.
What you want to do is called perfect forwarding, and there is a function in the STL that does it:
template <class T> T&& forward(typename remove_reference<T>::type& t) noexcept
{
return static_cast<T&&>(t)
}
template <class T> T&& forward(typename remove_reference<T>::type&& t) noexcept
{
return static_cast<T&&>(t)
}
You need the remove_reference
to avoid reference collapse. And when using it, you'll have to specify the type of the object you are trying to forward:
std::forward<X>(X("x2"));
Most things in programming languages are not completely and totally free. Unless you're writing compile-time only code, writing an identity function is unlikely to be free.
Let's rework your code a bit:
#include <algorithm>
#include <iostream>
template <typename T>
T id1(T&& t)
{
return t;
}
template <typename T>
T id2(T&& t)
{
return std::move(t);
}
class X
{
public:
X()
{ output0("Xdef"); }
X(std::string const& s) : label_(s)
{ output1("Xstr",s); }
X(X const& x) : label_(x.label_)
{ output1("Xcopy", x); }
X(X&& x) : label_(std::move(x.label_))
{ output1("Xmove", x); }
X& operator =(X const& x)
{
output1("operator =copy", x);
label_ = x.label_;
return *this;
}
X& operator =(X&& x)
{
using std::swap;
output1("operator =move", x);
swap(label_, x.label_);
return *this;
}
~X()
{ output0("~X"); }
private:
void output_id() const
{
std::cout << this << '[' << label_ << "]";
}
void output0(std::string const& name) const
{
output_id();
std::cout << ": " << name << "()" << std::endl;
}
void output1(std::string const& name, std::string const& str) const
{
output_id();
std::cout
<< ": " << name
<< "(\"" << str
<< "\")" << std::endl;
}
void output1(std::string const& name, X const& arg) const
{
output_id();
std::cout << ": " << name << '(';
arg.output_id();
std::cout << ')' << std::endl;
}
std::string label_;
};
int main()
{
{
std::cout << "CASE A:\n";
auto x = X("x1");
}
std::cout << "\n";
{
std::cout << "CASE B:\n";
auto x = id1(X("x2"));
}
std::cout << "\n";
{
std::cout << "CASE C:\n";
auto x = id2(X("x3"));
}
std::cout << "\n";
{
std::cout << "CASE D:\n";
X x = id1(X("x4"));
}
std::cout << "\n";
{
std::cout << "CASE E:\n";
X x = id2(X("x5"));
}
}
and when run it outputs (using a GCC v4.8 snapshot):
$ ./a.out
CASE A:
0x7fff411fc530[x1]: Xstr("x1")
0x7fff411fc530[x1]: ~X()
CASE B:
0x7fff411fc540[x2]: Xstr("x2")
0x7fff411fc520[x2]: Xcopy(0x7fff411fc540[x2])
0x7fff411fc540[x2]: ~X()
0x7fff411fc520[x2]: ~X()
CASE C:
0x7fff411fc540[x3]: Xstr("x3")
0x7fff411fc520[x3]: Xmove(0x7fff411fc540[])
0x7fff411fc540[]: ~X()
0x7fff411fc520[x3]: ~X()
CASE D:
0x7fff411fc540[x4]: Xstr("x4")
0x7fff411fc520[x4]: Xcopy(0x7fff411fc540[x4])
0x7fff411fc540[x4]: ~X()
0x7fff411fc520[x4]: ~X()
CASE E:
0x7fff411fc540[x5]: Xstr("x5")
0x7fff411fc520[x5]: Xmove(0x7fff411fc540[])
0x7fff411fc540[]: ~X()
0x7fff411fc520[x5]: ~X()
$
Case A simply invokes the constructor for X. The =
in this instance is equivalent to passing the right-hand side of the =
to X, i.e., it is not an assignment.
Case B invokes id1()
which does not move its return argument. Since the returned value was not defined on the call stack of id() and the value is an lvalue (holding an rvalue) it was not automatically moved upon return and therefore was copied.
Case C invokes id2()
which does invoke the move constructor on return.
Cases D and E are the same as Cases B and C respectively except that auto
is not used if you were sceptical about such.
Moves should be seen as optimized copies and as bad as copies in the worst case (although they will often be much better). Even an optimal move has a cost (e.g., copying some data (usually) from one stack frame to another). The only way copies/moves are completely avoided in run-time code is when return value optimization and copy ellison are eligible for the compiler to use.
精彩评论