プログラミング言語のほとんどのものは完全に完全に無料ではありません。コンパイル時のみのコードを書くのでない限り、アイデンティティ関数を書くことは自由ではないでしょう。
#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に渡すのと同じです。つまり、代入ではありません。
ケースBは、戻り引数を移動しないid1()
を呼び出します。戻り値はid()の呼び出しスタックで定義されておらず、値は左辺値(右辺値を保持)であるため、戻り時に自動的に移動せず、したがってコピーされました。
ケースCはid2()
を呼び出し、返されると移動コンストラクタを呼び出します。
ケースDとEはそれぞれケースBとCと同じですが、auto
が使用されていないという点を除いては同じです。
移動は最適化されたコピーと見なされますが、最悪の場合はコピーほど悪くなります(ただし、しばしばはるかに優れています)。最適な移動であってもコストがかかる(例えば、あるスタックフレームから別のスタックフレームに(通常は)データをコピーする)。実行時コードでコピー/移動が完全に回避される唯一の方法は、戻り値の最適化とコピーellisonがコンパイラの使用に適している場合です。
問題は、返された参照が引き続き渡された同じオブジェクトを参照しているかどうかを関数宣言が伝えないことです。コンパイラが関数の実装をチェックして、それが何を返すのかを判断し、この分析に依存して一時的なライフタイムを作るように強制するのは妥当ではありません。 – sellibitze