7

関数から右辺値参照を返すことに関するこの回答を読んで、どうすればidC++0x で関数を記述できるか考えさせられました。

基本的に、私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に観察可能な効果があります。

idC++0x で関数を作成するにはどうすればよいですか。特に、移動/コピー コンストラクターのない型で機能します。

4

3 に答える 3

7

できません。原則として、右辺値参照を返す関数を作成するべきではありません。正しく指摘したように、一時的なものの存続期間を十分に長くすることはできません。

于 2011-08-23T14:37:21.177 に答える
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 のコンストラクターを呼び出すだけです。=この場合の the は、の右辺=を X に渡すことと同じです。つまり、代入ではありません。

ケース B はid1()、戻り引数を移動しない呼び出しです。返された値は id() のコール スタックで定義されておらず、値は左辺値 (右辺値を保持) であるため、返されたときに自動的に移動されず、コピーされました。

ケース C はid2()、戻り時に移動コンストラクターを呼び出します。

ケース D と E はそれぞれケース B と C と同じですが、auto疑いがある場合は使用されません。

移動は最適化されたコピーと見なされ、最悪の場合はコピーと同じくらい悪いものと見なされます (ただし、多くの場合、はるかに優れています)。最適な移動にもコストがかかります (たとえば、あるスタック フレームから別のスタック フレームに (通常は) 一部のデータをコピーするなど)。実行時コードでコピー/移動を完全に回避する唯一の方法は、戻り値の最適化とコピー エリソンがコンパイラで使用できる場合です。

于 2012-07-14T00:37:45.937 に答える
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"));
于 2011-08-23T15:27:07.170 に答える