关于从函数返回rvalue引用的阅读this答案让我思考,如何在C ++ 0x中编写id
函数。
基本上,我希望id
是一个什么都不做的函数,一个对程序没有可观察影响的函数。
我的第一次尝试如下:
#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;
}
但是,我担心在这种情况下,x2是悬空参考,因为X("x2")
在“第2行”执行之前被销毁。
所以在这里,很明显id
具有可观察到的效果。
如何在C ++ 0x中编写id
函数,尤其适用于没有移动/复制构造函数的类型。
答案 0 :(得分:7)
你做不到。通常,您不应该编写返回rvalue引用的函数 - 正如您正确指出的那样,您不能将临时的生命周期延长到足够长的时间。
答案 1 :(得分:0)
您想要做的是完美转发,STL中有一个功能:
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)
}
您需要remove_reference
以避免引用崩溃。使用它时,您必须指定要转发的对象的类型:
std::forward<X>(X("x2"));
答案 2 :(得分:0)
编程语言中的大多数东西都不是完全免费的。除非您只编写编译时代码,否则编写身份函数不太可能是免费的。
让我们重新修改你的代码:
#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"));
}
}
运行时输出(使用GCC v4.8快照):
$ ./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()
$
案例A只是调用X的构造函数。此实例中的=
相当于将=
的右侧传递给X,即它不是赋值。
案例B调用id1()
,它不会移动其返回参数。由于返回的值没有在id()的调用堆栈上定义,并且值是左值(保持rvalue),因此在返回时不会自动移动,因此被复制。
案例C调用id2()
,它会在返回时调用移动构造函数。
案例D和E分别与案例B和案例C相同,但如果您对此案持怀疑态度,则不使用auto
。
在最坏的情况下,移动应被视为优化副本,与副本一样糟糕(尽管它们通常会好得多)。即使最佳移动也有成本(例如,将一些数据(通常)从一个堆栈帧复制到另一个堆栈帧)。在运行时代码中完全避免复制/移动的唯一方法是返回值优化和复制ellison符合编译器使用的条件。