dポイントプレゼントキャンペーン実施中!

コピーコンストラクタが呼び出されていない?

class myclass
{
public:
int x;
myclass(const int& init_); //コンストラクタ
myclass(const myclass& init_); //コピーコンストラクタ
const myclass operator+(const myclass& rhs); //加算
const myclass& operator=(const myclass& rhs); //代入
};
と定義したクラスを使ってオブジェクトを生成するときに妙な挙動をしています。

myclass mc1(10);
myclass mc2 = 20;
myclass mc3(mc1 + mc2); //コピーコンストラクタが呼ばれるはず
このコードを実行した結果は、mc3(mc1+mc2)では自分で定義したコピーコンストラクタは実行されませんでした。
mc3(mc1 = mc2)と実行すれば、コピーコンストラクタが実行されたのですが、この違いはどこにあるのでしょうか。

そもそも、myclassとmyclass&は全く別のものなのでしょうか?

A 回答 (2件)

規格の12.8の15節に載っている,コピーコンストラクタの省略ですね。



ISO/IEC 14882:2003 Programming languages - C++
12 Special member functions - 12.8 Copying class objects / 15
> When certain criteria are met, an implementation is allowed to omit the copy construction of a class object, even if the copy constructor and/or destructor for the object have side effects.
(略)
> - in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object with the same cv-unqualified type as the function return type, the copy operation can be omitted by constructing the automatic object directly into the function’s return value
> - when a temporary class object that has not been bound to a reference (12.2) would be copied to a class object with the same cv-unqualified type, the copy operation can be omitted by constructing the temporary object directly into the target of the omitted copy
日本語版 (JIS X 3014 プログラム言語C++) の記述だと,以下のようになっています。
12 特殊メンバ関数 - 12.8 クラスオブジェクトのコピー
> 処理系は,ある基準が満たされれば,そのクラスオブジェクトのコピーによる構築を省略してよい。これは,このオブジェクトのコピーコンストラクタ 及び/又は デストラクタが副作用を持つ場合も含む。
(略)
> -返却値の型がクラスである関数の return 文の式が,関数の返却値の型と同じ cv 修飾なしの型の volatile でない自動記憶域期間のオブジェクトの名前の場合,自動記憶域期間のオブジェクトを直接関数の返却値として構築することで,コピー演算は省略できる
> -参照に結合していない一時クラスオブジェクトがそれと同じ cv 修飾なしの型をもつクラスオブジェクトにコピーされる場合,一時オブジェクトを直接そのコピー先に構築することで,コピー演算は省略できる。

前者は関数内のreturn文の話なので,今回は関係しません。
なので後者について,元のコードを見ていきます。
> const myclass operator+(const myclass& rhs); //加算
> myclass mc3(mc1 + mc2); //コピーコンストラクタが呼ばれるはず
・mc1 + mc2は参照に結合していない一時クラスオブジェクトを返す
・mc1 + mc2の一時クラスオブジェクトの型はconst myclassで,新たに構築されるmc3の型はmyclass
より,後者の条件を満たします。
この場合,省略は「allowed」 (JISでは「してよい」) なので,コンパイラは省略することも省略しないことも許されます。
# 副作用の有無によらないので,コピーコンストラクタの中で複雑なことをやっていても無視できます。

次に,
> const myclass& operator=(const myclass& rhs); //代入
> myclass mc3(mc1 = mc2);
・mc = m1は参照に結合しているクラスオブジェクトを返す
ので,後者の条件を満たしません。
コンパイラはコピーコンストラクタの呼び出しの省略が規格に許されていませんから,コンパイラはコピーコンストラクタを呼び出すコードを記述します。
    • good
    • 0

コンパイラに依存する問題な気がします。

的外れな解答になったらすいません。
たとえば、次のような実装が行われていて、

myclass::myclass(const int& init_)
{
// (A)
x = init_;
}

myclass::myclass(const myclass& init_)
{
x = init_.x;
}

const myclass myclass::operator+(const myclass& rhs)
{
return myclass(x + rhs.x);// (B)
}

で、以下のコードを実行したとき、

myclass mc1(10);
myclass mc2 = 20;
myclass mc3(mc1 + mc2);
// (C)

myclass mc3(mc1 + mc2); の実行の時に、上の(B)行の
myclass::myclass(10 + 20); // (const int& のほう)
が呼ばれた形跡があるのだけど、

”そのあとに myclass::myclass(const myclass& init_); が呼ばれると思ったが、呼び出されないのはなぜ???”

という話でしょうか?
もし、そうなら、上の(A) の位置のthisポインタの値と、(C)の行の mc3 のアドレス
が一致しているか見てみてください。
(デバッガで追えるならデバッガで、printfがログに使えるのなら)

myclass::myclass(const int& init_)
{
// (A)
printf("constructor:0x%p\n", this);
x = init_;
}

myclass mc1(10);
myclass mc2 = 20;
myclass mc3(mc1 + mc2);
// (C)
printf("mc3:0x%p\n", &mc3);

もし、アドレスが一致しているのなら、コンパイラによる
”局所オブジェクトの削除”の最適化が行われているのでは?と思います。
    • good
    • 0
この回答へのお礼

まさに回答して頂いた説明の内容を確認したところ、その通りの結果となりました。
(B)の所に直接コンストラクタを使用する形で、return myclass(x + rhs.x)と書いたときには、このオブジェクトがmc3のオブジェクトのポインタと同じになりました。
また、(B)を以下のように書くとコピーコンストラクタが呼び出されました。
myclass retv(x + rhs.x);
return retv;

今更書いても遅いですが、このプログラムはVisualStudio2008で作成しており、最適化を無効化して実験しました。最適化のオプションを付けると、下のコードでもコピーコンストラクタは呼び出されませんでした。
コンパイラ凄いですね......

お礼日時:2010/03/20 00:24

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!