2009-04-27 4 views
4

私は、自己定義コンストラクタ、デストラクタ、コピーコンストラクタ、代入演算子も含むサンプルクラスを持つ小さなテストプログラムを作成しました。私はコピーコンストラクタが、すべてで呼び出されていなかったことに気づいたとき、私は私がObject o1; Object o2(o1);のように私のクラスの戻り値を持つ関数や線を実装していても、驚いた明示的なコピーコンストラクタを使用するようにコンパイラを強制するには?

innerclass.hpp:

#include <iostream> 

class OuterClass 
{ 
public: 
OuterClass() 
{ 
    std::cout << "OuterClass Constructor" << std::endl; 
} 
~OuterClass() 
{ 
    std::cout << "OuterClass Destructor" << std::endl; 
} 
OuterClass(const OuterClass & rhs) 
{ 
    std::cout << "OuterClass Copy" << std::endl; 
} 
OuterClass & operator=(const OuterClass & rhs) 
{ 
    std::cout << "OuterClass Assignment" << std::endl; 
} 

class InnerClass 
{ 
public: 
    InnerClass() : m_int(0) 
    { 
     std::cout << "InnerClass Constructor" << std::endl; 
    } 
    InnerClass(const InnerClass & rhs) : m_int(rhs.m_int) 
    { 
     std::cout << "InnerClass Copy" << std::endl; 
    } 
    InnerClass & operator=(const InnerClass & rhs) 
    { 
     std::cout << "InnerClass Assignment" << std::endl; 
     m_int = rhs.m_int; 
     return *this; 
    } 
    ~InnerClass() 
    { 
     std::cout << "InnerClass Destructor" << std::endl; 
    } 
    void sayHello() 
    { 
     std::cout << "Hello!" << std::endl; 
    } 

private: 
    int m_int; 
}; 

InnerClass innerClass() 
{ 
    InnerClass ic; 
    std::cout << "innerClass() method" << std::endl; 
    return ic; 
} 
}; 

innerclass.cpp:

#include "innerclass.hpp" 

int main(void) 
{ 
std::cout << std::endl << "1st try:" << std::endl; 


OuterClass oc; 
OuterClass oc2(oc); 
oc.innerClass().sayHello(); 

std::cout << std::endl << "2nd try:" << std::endl; 

OuterClass::InnerClass ic(oc.innerClass()); 
ic = oc.innerClass(); 
} 

出力:

1st try: 
OuterClass Constructor 
OuterClass Copy 
InnerClass Constructor 
innerClass() method 
Hello! 
InnerClass Destructor 

2nd try: 
InnerClass Constructor 
innerClass() method 
InnerClass Constructor 
innerClass() method 
InnerClass Assignment 
InnerClass Destructor 
InnerClass Destructor 
OuterClass Destructor 
OuterClass Destructor 

A私は、コンパイラが明示的に定義されたコピーコンストラクタを使用するという保証はない、と私は読んでいます。私はこの行動を理解していません。コピーコンストラクタはなぜ呼び出されるのかわからないと、それでも存在しますか?コンパイラはそれを使用するかどうかをどのように決定しますか?

さらに、コンパイラに自己定義コピーコンストラクタを使用させる方法がありますか?

+0

はあなたが読んでいたものにリンクすることはできますか?私は何かを誤解していると思う。 –

+0

いくつかのコードを投稿してください。コピーコンストラクタの定義方法を確認する必要があります。 –

+0

o1がObjectによって構成可能な他の型に暗黙的に変換されていないことを確認しますか?特定のコードがなければ、コードが失敗する理由を伝える本当の方法はありません。 –

答えて

6

ただ、他の回答との完全を期すために、標準のコンパイラがコピーを省略することができます(戻り値の最適化または名前付き戻り値の最適化 - RVO/NRVOと呼ばれるもの):

個の

12.8コピークラスのオブジェクト、段落15の一時的なクラス・オブジェクトがコピーコンストラクタを使用してコピーされるたびに(C++ 98)

、このオブジェクトとコピーは同じCV-非修飾型が、実装されますクラスコピーコンストラクタまたはデストラクタが副作用を持っていても、オリジナルとコピーを同じオブジェクトを参照する2つの異なる方法として扱い、コピーをまったく実行しないことを許可しました。クラス戻り型の関数の場合、return文の式がローカルオブジェクトの名前で、ローカルオブジェクトのcv非修飾型が関数の戻り値の型と同じ場合、実装では作成を省略できますクラスコピーコンストラクタまたはデストラクタに副作用があっても、関数の戻り値を保持する一時オブジェクト。このような場合、オブジェクトは、オリジナルとコピーが最適化されずに破棄された後に破棄されます。

だからあなたinnerClass()方法では、あなたがリターンで呼ばれることだろうと思うかもしれませんコピーコンストラクタを離れて最適化することが許可されている:

InnerClass innerClass() { 
    InnerClass ic; 
    std::cout << "innerClass() method" << std::endl; 
    return ic; // this might not call copy ctor 
} 
+0

+1これを追加するには、これは議論の余地がありますw.r.t. "副作用"の側面。ご覧のとおり、この最適化によってプログラムの出力が変更されます。 Yuk! –

4

特定の状況で呼び出される(または呼び出されない)コピーコンストラクタに依存して、クラスを設計しないでください。コンパイラーは、あらゆる場所でコピーコンストラクター呼び出しを削除または追加することができます。実際には、それらを追跡する必要はありません。

なぜなら、1つの井戸が必要な理由として、コンパイラーはコピーが必要であると判断し、コピーコンストラクターはそれを行うために使用するものです。コピーコンストラクタ呼び出しの利点は、パフォーマンスコピーは通常非常に高価な操作です。

+0

コピーコンストラクタはおそらく呼び出されているかもしれませんし、そうでないかもしれません。しかし、*必要な場合は、明示的に定義されたものと、コンパイラによって自動生成されたものは例外なく、常に存在します。 – Chris

+0

それは正しいです。 –

+1

つまり、特定の状況下でコンパイラが呼び出しを削除する余裕があるため、コピーコンストラクタに副作用を入れないでください。 –

0

Object o1();は、オブジェクトを作成せず、関数名o1、void引数、戻り値の型をObjectとして持つ関数プロトタイプを定義します。あなたは実際の問題を見つけるためにいくつかのコードを投稿する必要があります。

+0

良い点! Scot Meyers Effective-STLの本に名前が書かれているように、「The C++ most vexing parse」です。そのような誤った構成の別の例: リストデータ(istream_iterator 、istream_iterator ()); –

+0

その1つはタイプミスでした、それは申し訳ありません。 – Chris

4

私はNeilに同意します。呼び出されるコピーコンストラクタに依存するクラスを書くべきではありません。つまり、コンパイラは多くの戻り値のシナリオでコピーコンストラクタを完全に回避する "名前付き戻り値の最適化"(link)のようなことを行うことができるからです。コピーコンストラクタの呼び出しを強制するには、C++コンパイラを "トリッキー"にするための多くのコードを記述する必要があります。いい考えではない。

特定のシナリオでは、コピーコンストラクタの呼び出しを強制する場合は、明示的な呼び出しを行うことができます。

Object SomeFunc() { 
    Object o1 = ... 
    return Object(o1); 
} 
+0

あなたの答えをありがとう。あなたの解決策では、コピーコンストラクタが2回呼び出される可能性がありますか?最初にObject(o1)を呼び出すと、実際に値を返す2回目ですか? – Chris

+0

@Chrisはい、可能です。戻り値の最適化は、C++コンパイラの一部です。私は親密には慣れていないので、二重のコピーに対して確実かどうかは確信できません。私は信じています。 – JaredPar

+0

しかし、この状況では(コンパイラがRVOを実行しないことを決定した場合)、コピーctorが2回呼び出される可能性があることに気付きます。例えば、VC9では、innerClass()メソッドの戻り値を 'InnerClass(ic)を返す 'に変更すると、コピーctorが2回呼び出されます。 –

0

いくつかのコードを投稿してください。私はいくつかの誤った構文を使用していると思います。

コピーコンストラクタは正確に次のシグネチャが必要です。

MyObject(const MyObject&) 

その後、あなたはコピーコンストラクタは次のコードで呼び出されたかどうかを確認することができます

MyObject m1; 
MyObject m2(m1); 

あなたが使用を許可されていませんMyObject m1();それは関数宣言です。

+0

これは私のやり方です。 – Chris

2

これが問題ですか?

OuterClass(const OuterClass & rhs) 
{   
std::cout << "OuterClass Constructor" << std::endl; 
==> 
std::cout << "OuterClass Copy Constructor" << std::endl; 

} 

OuterClass & operator=(const OuterClass & rhs) 
{  
std::cout << "OuterClass Constructor" << std::endl; 
==> 
std::cout << "OuterClass Assignment operator" << std::endl; 
} 

コピー貼り付けエラー!

コードをデバッグして正確に何が起こっているかを確認することをお勧めします。デバッグは実際に問題を見つけるのに役立ちます。

EDIT:内部クラスの問題のため

他の人が既にこの名前戻り値の最適化(NRVO)の場合で指摘したように。

InnerClass innerClass() 
{  
    InnerClass ic;   
    std::cout << "innerClass() method" << std::endl;   
    return ic; 
} 

コンパイラは、このように、それは、クラスオブジェクトの値によってリターンとクラスのコピーコンストラクタを呼び出す必要があるの両方を排除

void innerClass(InnerClass &namedResult) 

{ 
std::cout << "innerClass() method" << std::endl; 

} 

に機能を変換することができます。

NRVOにもっと理解するために、2つのリンクの下に通過してください:

+0

ありがとう、それは本当に "OuterClass"のエラーでした。問題がまだ存在する「InnerClass」に焦点を当てていたので、私はそれを認識しませんでした。 – Chris

関連する問題