459

コピーの省略とは何ですか?(名前付き)戻り値の最適化とは何ですか?それらは何を意味しますか?

どのような状況で発生する可能性がありますか?制限は何ですか?

4

5 に答える 5

310

序章

技術的な概要については、この回答にスキップしてください。

コピーの省略が発生する一般的なケースの場合-この回答にスキップしてください。

コピーの省略は、特定の状況で余分な(潜在的に高価な)コピーを防ぐために、ほとんどのコンパイラーによって実装される最適化です。これにより、実際には値または値渡しでの返品が可能になります(制限が適用されます)。

これは、あたかもルールを排除する(ha!)唯一の最適化の形式です。オブジェクトのコピー/移動に副作用がある場合でも、コピーの省略を適用できます

ウィキペディアから抜粋した次の例:

struct C {
  C() {}
  C(const C&) { std::cout << "A copy was made.\n"; }
};
 
C f() {
  return C();
}
 
int main() {
  std::cout << "Hello World!\n";
  C obj = f();
}

コンパイラと設定に応じて、次の出力はすべて有効です

「こんにちは世界」
コピーが作成されました。
コピーが作成されました。


「こんにちは世界」
コピーが作成されました。


「こんにちは世界」

これは、作成できるオブジェクトが少なくなることも意味するため、呼び出される特定の数のデストラクタに依存することもできません。コピー/移動コンストラクタまたはデストラクタの内部に重要なロジックを含めるべきではありません。これらが呼び出されることに依存することはできないからです。

コピーまたは移動コンストラクターの呼び出しが省略された場合、そのコンストラクターはまだ存在し、アクセス可能である必要があります。これにより、コピーの省略により、通常はコピーできないオブジェクトをコピーできなくなります。たとえば、プライベートまたは削除されたコピー/移動コンストラクターがあるためです。

C ++ 17:C ++ 17以降、オブジェクトが直接返される場合、コピーの省略が保証されます。

struct C {
  C() {}
  C(const C&) { std::cout << "A copy was made.\n"; }
};
 
C f() {
  return C(); //Definitely performs copy elision
}
C g() {
    C c;
    return c; //Maybe performs copy elision
}
 
int main() {
  std::cout << "Hello World!\n";
  C obj = f(); //Copy constructor isn't called
}
于 2012-10-18T11:03:03.340 に答える
112

標準リファレンス

あまり技術的でない見解と紹介については、この回答にスキップしてください。

コピーの省略が発生する一般的なケースの場合-この回答にスキップしてください。

コピーの省略は、次の標準で定義されています。

12.8クラスオブジェクトのコピーと移動[class.copy]

なので

31)特定の基準が満たされると、オブジェクトのコピー/移動コンストラクタおよび/またはデストラクタに副作用がある場合でも、実装はクラスオブジェクトのコピー/移動構築を省略できます。このような場合、実装は、省略されたコピー/移動操作のソースとターゲットを、同じオブジェクトを参照する2つの異なる方法として扱い、そのオブジェクトの破棄は、2つのオブジェクトが最適化なしで破棄されました。123コピーの省略と呼ばれるこのコピー/移動操作の省略は、次の状況で許可されます(複数のコピーを削除するために組み合わせることができます)。

—クラスreturn型を持つ関数のreturnステートメントで、式が関数return型と同じcvunqualified型を持つ不揮発性自動オブジェクト(関数またはcatch-clauseパラメーター以外)の名前である場合、自動オブジェクトを関数の戻り値に直接構築することにより、コピー/移動操作を省略できます。

— throw-expressionで、オペランドが不揮発性の自動オブジェクト(関数またはcatch-clauseパラメーターを除く)の名前であり、そのスコープが最も内側の囲んでいるtry-blockの終わりを超えない場合(存在する場合) 1)、オペランドから例外オブジェクト(15.1)へのコピー/移動操作は、自動オブジェクトを例外オブジェクトに直接構築することで省略できます。

—参照(12.2)にバインドされていない一時クラスオブジェクトが同じcv-unqualifiedタイプのクラスオブジェクトにコピー/移動される場合、一時オブジェクトを直接に構築することにより、コピー/移動操作を省略できます。省略されたコピー/移動のターゲット

—例外ハンドラの例外宣言(15節)が例外オブジェクト(15.1)と同じタイプのオブジェクト(cv-qualificationを除く)を宣言する場合、例外宣言を処理することでコピー/移動操作を省略できます。例外宣言によって宣言されたオブジェクトのコンストラクタとデストラクタの実行を除いて、プログラムの意味が変更されない場合は、例外オブジェクトのエイリアスとして。

123)2つではなく1つのオブジェクトのみが破棄され、1つのコピー/移動コンストラクターが実行されないため、構築されるオブジェクトごとに1つのオブジェクトが破棄されます。

与えられた例は次のとおりです。

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
Thing f() {
  Thing t;
  return t;
}
Thing t2 = f();

と説明:

ここで、省略の基準を組み合わせて、クラスのコピーコンストラクターへの2つの呼び出しを排除できます。関数の戻り値の一時オブジェクトへのThingローカル自動オブジェクト のコピーと、その一時オブジェクトのオブジェクトへのコピーです。事実上、ローカルオブジェクトの構築は 、グローバルオブジェクトを直接初期化するものと見なすことができ、そのオブジェクトの破棄はプログラムの終了時に発生します。Thingにmoveコンストラクターを追加しても同じ効果がありますが、一時オブジェクトから省略されるのはmoveコンストラクターです。tf()t2tt2t2

于 2012-10-18T11:03:51.627 に答える
109

コピーの省略の一般的な形式

技術的な概要については、この回答にスキップしてください。

あまり技術的でない見解と紹介については、この回答にスキップしてください。

(名前付き)戻り値の最適化は、コピーの省略の一般的な形式です。これは、メソッドから値によって返されるオブジェクトのコピーが削除される状況を指します。オブジェクトに名前が付けられているため、標準に示されている例は、名前付き戻り値の最適化を示しています。

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
Thing f() {
  Thing t;
  return t;
}
Thing t2 = f();

通常の戻り値の最適化は、一時的なものが返されるときに発生します。

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
Thing f() {
  return Thing();
}
Thing t2 = f();

コピーの省略が行われる他の一般的な場所は、一時的な値が渡される場合です

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
void foo(Thing t);

foo(Thing());

または、例外がスローされて値によってキャッチされた場合

struct Thing{
  Thing();
  Thing(const Thing&);
};
 
void foo() {
  Thing c;
  throw c;
}
 
int main() {
  try {
    foo();
  }
  catch(Thing c) {  
  }             
}

コピーの省略の一般的な制限は次のとおりです。

  • 複数のリターンポイント
  • 条件付き初期化

ほとんどの商用グレードのコンパイラは、コピーの省略と(N)RVOをサポートしています(最適化設定によって異なります)。

于 2012-10-18T11:04:05.947 に答える
69

コピーの省略は、オブジェクトの不要なコピー/移動を排除するコンパイラ最適化手法です。

次の状況では、コンパイラーはコピー/移動操作を省略できるため、関連するコンストラクターを呼び出せません。

  1. NRVO(Named Return Value Optimization):関数が値によってクラスタイプを返し、returnステートメントの式が自動保存期間(関数パラメーターではない)を持つ非揮発性オブジェクトの名前である場合、コピー/移動最適化されていないコンパイラによって実行されることは省略できます。その場合、戻り値は、関数の戻り値が移動またはコピーされるストレージに直接作成されます。
  2. RVO(戻り値の最適化):関数が、単純なコンパイラーによって宛先に移動またはコピーされる名前のない一時オブジェクトを返す場合、1のようにコピーまたは移動を省略できます。
#include <iostream>  
using namespace std;

class ABC  
{  
public:   
    const char *a;  
    ABC()  
     { cout<<"Constructor"<<endl; }  
    ABC(const char *ptr)  
     { cout<<"Constructor"<<endl; }  
    ABC(ABC  &obj)  
     { cout<<"copy constructor"<<endl;}  
    ABC(ABC&& obj)  
    { cout<<"Move constructor"<<endl; }  
    ~ABC()  
    { cout<<"Destructor"<<endl; }  
};

ABC fun123()  
{ ABC obj; return obj; }  

ABC xyz123()  
{  return ABC(); }  

int main()  
{  
    ABC abc;  
    ABC obj1(fun123());    //NRVO  
    ABC obj2(xyz123());    //RVO, not NRVO 
    ABC xyz = "Stack Overflow";//RVO  
    return 0;  
}

**Output without -fno-elide-constructors**  
root@ajay-PC:/home/ajay/c++# ./a.out   
Constructor    
Constructor  
Constructor  
Constructor  
Destructor  
Destructor  
Destructor  
Destructor  

**Output with -fno-elide-constructors**  
root@ajay-PC:/home/ajay/c++# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors    
root@ajay-PC:/home/ajay/c++# ./a.out   
Constructor  
Constructor  
Move constructor  
Destructor  
Move constructor  
Destructor  
Constructor  
Move constructor  
Destructor  
Move constructor  
Destructor  
Constructor  
Move constructor  
Destructor  
Destructor  
Destructor  
Destructor  
Destructor  

コピーの省略が発生し、copy- / move-constructorが呼び出されない場合でも、コピーが存在し、アクセス可能である必要があります(最適化がまったく行われなかったかのように)。そうでない場合、プログラムの形式が正しくありません。

このようなコピーの省略は、ソフトウェアの観察可能な動作に影響を与えない場所でのみ許可する必要があります。コピーの省略は、観察可能な副作用をもたらす(つまり、排除する)ことが許可されている唯一の最適化の形式です。例:

#include <iostream>     
int n = 0;    
class ABC     
{  public:  
 ABC(int) {}    
 ABC(const ABC& a) { ++n; } // the copy constructor has a visible side effect    
};                     // it modifies an object with static storage duration    

int main()   
{  
  ABC c1(21); // direct-initialization, calls C::C(42)  
  ABC c2 = ABC(21); // copy-initialization, calls C::C( C(42) )  

  std::cout << n << std::endl; // prints 0 if the copy was elided, 1 otherwise
  return 0;  
}

Output without -fno-elide-constructors  
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp  
root@ajay-PC:/home/ayadav# ./a.out   
0

Output with -fno-elide-constructors  
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors  
root@ajay-PC:/home/ayadav# ./a.out   
1

GCCには、-fno-elide-constructorsコピーの省略を無効にするオプションがあります。コピーの省略の可能性を回避したい場合は、を使用して-fno-elide-constructorsください。

現在、ほとんどすべてのコンパイラは、最適化が有効になっている場合(および、それを無効にする他のオプションが設定されていない場合)にコピーの省略を提供します。

結論

コピーの省略ごとに、コピーの1つの構築と1つの一致する破棄が省略されるため、CPU時間が節約され、1つのオブジェクトが作成されないため、スタックフレームのスペースが節約されます。

于 2015-01-13T07:26:05.853 に答える
-2

ここでは、今日私が明らかに遭遇したコピーの省略の別の例を示します。

# include <iostream>


class Obj {
public:
  int var1;
  Obj(){
    std::cout<<"In   Obj()"<<"\n";
    var1 =2;
  };
  Obj(const Obj & org){
    std::cout<<"In   Obj(const Obj & org)"<<"\n";
    var1=org.var1+1;
  };
};

int  main(){

  {
    /*const*/ Obj Obj_instance1;  //const doesn't change anything
    Obj Obj_instance2;
    std::cout<<"assignment:"<<"\n";
    Obj_instance2=Obj(Obj(Obj(Obj(Obj_instance1))))   ;
    // in fact expected: 6, but got 3, because of 'copy elision'
    std::cout<<"Obj_instance2.var1:"<<Obj_instance2.var1<<"\n";
  }

}

結果:

In   Obj()
In   Obj()
assignment:
In   Obj(const Obj & org)
Obj_instance2.var1:3
于 2020-10-15T14:27:18.550 に答える