コンストラクタとデストラクタ 解答ページ | Programming Place Plus Modern C++編【言語解説】 第7章

トップページModern C++編 C++編](../../index.html) – 第7章

Modern C++編は作りかけで、更新が停止しています。代わりに、C++14 をベースにして、その他の方針についても見直しを行った、新C++編を作成しています。
Modern C++編は削除される予定です。

問題①

問題① 次のようにコンストラクタを定義することには問題があります。指摘してください。

class Student {
public:
    Student(const char* name, int grade, int score);
    Student(const char* name, int score, int grade);
};


コンストラクタを複数定義できますが、実引数によって区別が付くものでなければなりません。このプログラムの場合、引数の並びが「const char*、int、int」のようにまったく同じになっており、引数の名前にしか違いがなく、区別を付けられませんから、コンパイルエラーになります。

問題②

問題② メンバイニシャライザを使った初期化と、コンストラクタ内で代入によって初期値を設定する方法とで、パフォーマンスにどの程度の違いがあるか、計測してください(パフォーマンス測定マクロが、コードライブラリにあります)


適当なクラス型のメンバ変数^を持ったクラスを2つ用意して、片方はメンバイニシャライザを使い、他方はコンストラクタ内で代入するようにします。

#include "ppp_perform.h"

class Rect {
public:
    Rect() = default;
    Rect(double left, double right, double top, double bottom) :
        mLeft(left), mRight(right), mTop(top), mBottom(bottom)
    {}

    void Set(double left, double right, double top, double bottom)
    {
        mLeft = left;
        mRight = right;
        mTop = top;
        mBottom = bottom;
    }

private:
    double  mLeft;
    double  mRight;
    double  mTop;
    double  mBottom;
};

class Test1 {
    Rect  mR1;
    Rect  mR2;
    Rect  mR3;

public:
    Test1();
};

Test1::Test1()
{
    mR1.Set(10.0, 10.0, 30.5, 30.5);
    mR2.Set(15.5, 10.0, 52.5, 52.5);
    mR3.Set(15.5, 15.5, 52.5, 52.5);
}


class Test2 {
    Rect  mR1;
    Rect  mR2;
    Rect  mR3;

public:
    Test2();
};

Test2::Test2() :
    mR1(10.0, 10.0, 30.5, 30.5),
    mR2(15.5, 10.0, 52.5, 52.5),
    mR3(15.5, 15.5, 52.5, 52.5)
{
}


int main()
{
    PPP_CHECK_PERFORM_BEGIN(10000000);
    Test1 t1;
    PPP_CHECK_PERFORM_END("don't use member_initializer");

    PPP_CHECK_PERFORM_BEGIN(10000000);
    Test2 t2;
    PPP_CHECK_PERFORM_END("use member_initializer");
}

実行結果:

don't use member_initializer: 0.847000 seconds
use member_initializer: 0.812000 seconds

わずかですが、メンバイニシャライザを使う方が高速になることが分かります。メンバ変数の個数がもっと多かったり、もっと複雑な型であったり、インスタンス化するオブジェクトの数が多かったりすると、さらにこの差が大きくなっていきますから、可能である限り、つねにメンバイニシャライザを使うようにしてください。

問題③

問題③ int型の変数の値を退避(保存)させておき、最後に確実に元の値を復元することをサポートするようなクラスを設計してください。つまり、次のような挙動になるようにしてください (X がクラスとします)。

int value = 10;

// この関数を呼び出したときに value が 10 なら、
// 抜け出した後も確実に 10 であるようにしたい。
void func(bool flag)
{
    X store(/* 引数は任意 */);

    value = 50;

    if (flag) {
        return;
    }

    value = 100;
}


このクラスは、コンストラクタで int型の変数を指定すれば、その時点での変数の値を保存しておき、デストラクタでその値へ確実に復元してくれます。ここでは、ValueStore という名前のクラスにしてみます。

// ValueStore.h

#ifndef VALUE_STORE_H_INCLUDED
#define VALUE_STORE_H_INCLUDED

// int型の変数の値を退避し、破棄時に復元する
class ValueStore {
public:
    ValueStore(int* ptr) :
        mPtr(ptr), mSaveValue(*ptr)
    {
    }

    ~ValueStore()
    {
        *mPtr = mSaveValue;
    }

private:
    int* const    mPtr;        // 対象の変数のメモリアドレス
    const int     mSaveValue;  // 元の値
};

#endif
// main.cpp

#include <iostream>
#include "ValueStore.h"

namespace {

int value = 10;

// この関数を呼び出したときに value が 10 なら、
// 抜け出した後も確実に 10 であるようにしたい。
void func(bool flag)
{
    ValueStore store(&value);

    value = 50;

    if (flag) {
        return;
    }

    value = 100;
}

}

int main()
{
    func(true);
    std::cout << value << std::endl;

    func(false);
    std::cout << value << std::endl;
}

実行結果:

10
10

デストラクタを利用することによって、func関数内をどのような形で抜け出すとしても気にすることなく、確実に復元処理が行われます。この例でいえば、途中で return する経路もありますが、ここを通っても通らなくても問題ありません。

このように、デストラクタは必ずしも、解放やファイルクローズといった、後片付けのような処理にしか使えないわけではありません。「確実に行ってほしいこと」を書くことができる、非常に役に立つ機能です。


参考リンク


更新履歴

’2018/9/5 全体的に見直し修正。

’2017/7/17 新規作成。



第7章のメインページへ

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

Programming Place Plus のトップページへ



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