コピー 解答ページ | Programming Place Plus C++編【言語解説】 第17章

トップページC++編

C++編で扱っている C++ は 2003年に登場した C++03 という、とても古いバージョンのものです。C++ はその後、C++11 -> C++14 -> C++17 -> C++20 -> C++23 と更新されています。
なかでも C++11 での更新は非常に大きなものであり、これから C++ の学習を始めるのなら、C++11 よりも古いバージョンを対象にするべきではありません。特に事情がないなら、新しい C++ を学んでください。 当サイトでは、C++14 をベースにした新C++編を作成中です。

問題①

問題① 次のプログラムはコンパイルエラーになります。理由を説明してください。

#include <string>

void func(std::string& s) {}

int main()
{
    func("abc");
}


実引数 “abc” は、名前がない rvalue です。対して、func関数の仮引数は、const が付いていない参照です。const無しの参照は lvalue しか受け付けないので、コンパイルエラーになります。

関数内で、参照を経由して内容を書き換えて欲しいのでない限りは、仮引数に参照を渡すときには const を付けるのが基本です。この例の場合、実引数は rvalue なので、そもそも書き換えることができませんし、意味がありません。

問題②

問題② ある関数の仮引数が次のようになっているとき、実引数が渡される際に何が行われているか説明してください。

void func(std::string s);
void func2(const std::string& s);


func関数のほうは、std::string型の値渡しになっています。実引数として std::string型の値を渡したのなら、仮引数s はコピーコンストラクタによって生成されます。

実引数に char型の配列を指定した場合も、適切にコンパイルされます。

char name[64];
const char* str = "xyz";

func(name);  // OK
func(str);   // OK

これは、std::string のコンストラクタに、仮引数が const char* なものがあるからです。

func2関数のほうは、仮引数が const参照になっています。そのため、実引数がコピーされることはなく、実引数を参照します。したがって、コンストラクタやコピーコンストラクタが呼び出されることもありません。

const std::string& という型の参照が、char型の配列を参照することはできませんが、以下のコードはコンパイルできます。

char name[64];
const char* str = "xyz";

func2(name);  // OK
func2(str);   // OK

これがコンパイルできるのは、name や str をコピーした一時オブジェクトが生成され、それを参照するからです。一時オブジェクトを参照できるのは、const参照の場合だけなので、もし、仮引数が constなしの参照だったらコンパイルエラーになります(問題①と同じ状況)。

問題③

問題③ 次のプログラムのコメント部分では何が行われているかを、特に、コンストラクタ、コピーコンストラクタ、operator=、デストラクタといった関数のどれが呼び出されているのかという観点から説明してください。

class MyClass {
};

MyClass func1(MyClass mc)
{
    return mc;
}

MyClass* func2(MyClass* mc)
{
    return mc;
}

MyClass& func3(MyClass& mc)
{
    return mc;
}

int main()
{
    MyClass a;       // A
    MyClass b = a;   // B
    MyClass c(b);    // C
    MyClass* d;      // D

    c = a;           // E

    c = func1(a);    // F
    d = func2(&a);   // G
    c = func3(a);    // H
}

Aは、特に初期値を与えていませんから、デフォルトコンストラクタが呼び出されます。MyClass には明示的なコンストラクタを定義していないので、コンパイラが自動的に生成したデフォルトコンストラクタが使用されます。

Bは、b と同じ型の a を初期値として与えていますから、コピーコンストラクタが呼び出されます。MyClass には明示的なコンストラクタを定義していないので、コンパイラが自動的に生成したコピーコンストラクタが使用されます。

Cは、「MyClass c = b;」と事実上同じ意味になりますから、コピーコンストラクタが呼び出されます(もし、c と b の型が異なっていたら、より複雑な処理が行われるかもしれません。本編のコラム参照)。

Dは、ポインタ変数の宣言なので、MyClass型のオブジェクトが作られるわけではありません。したがって、MyClass のコンストラクタが呼び出されることはありません。単に、未初期化で、どこも指していないポインタ変数が宣言されるだけです。

Eは、c も a も、MyClass型なので、「MyClass::operator=(const MyClass&)」を使用してコピーされます。operator= は明示的に定義していないので、コンパイラが自動的に生成したものが使用されます。

Fは、func1関数の仮引数は MyClass型なので、mc を新規のオブジェクトとして生成する必要があります。事実上、「MyClass mc = a;」と同じことをするので、func1関数の実引数を使って、コピーコンストラクタが呼ばれます。

また、「return mc;」によって MyClass型のオブジェクトを返しており、これを c で受け取っています。ここで返されるのは、仮引数mc をコピーして作られた一時オブジェクトです。そのため、コピーコンストラクタが1度呼び出されます。

これに加えて、すでに存在している c に対するコピーが起こるので、operator= も呼び出されます。結果、Fの部分では、仮引数mc のためにコピーコンストラクタが、return するオブジェクトのためにコピーコンストラクタが、c が受け取る際に operator= が呼び出されています。

Gは、func2関数の仮引数 mc は MyClass*型なので、単なるポインタ変数のやり取りです。戻り値も同様で、ポインタ変数を返すだけですから、MyClass のメンバ関数が呼び出されることはありません。

Hは、func3関数の仮引数 mc は参照なので、事実上「MyClass& mc = a;」と同じことです。これは新規のオブジェクトを作る訳でも無ければ、コピーを作る訳でもないので、MyClass のメンバ関数が呼び出されることはありません。

一方、戻り値の型も参照になっていて、これを c で受け取っています。c はすでに存在している実体なので、参照先にあるものをコピーするため、operator=() が呼び出されます。


参考リンク


更新履歴

’2018/9/14 第16章から、練習問題①を移動してきて③にした。

’2014/8/23 新規作成。



第17章のメインページへ

C++編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る