動的なオブジェクトの生成 解答ページ | Programming Place Plus C++編【言語解説】 第14章

トップページ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 <iostream>

int main()
{
    static const int ARRAY_SIZE = 10;

    int* array = new int[ARRAY_SIZE];
    for (int i = 0; i < ARRAY_SIZE; ++i) {
        array[i] = i;
    }

    for (int i = 0; i < ARRAY_SIZE; ++i) {
        std::cout << array[i] << std::endl;
    }

    delete array;
}


new演算子が配列版でありながら、delete演算子が非配列版を使われています。正しくは、

delete [] array;

としなければいけません。

問題②

問題② デストラクタを利用すれば、delete演算子の呼び忘れを防げます。new演算子で確保された MyClass型のポインタを対象に、そのような呼び忘れを防ぐためのクラスを設計してください。


まず、MyClassクラスを定義します。

// MyClass.h

#ifndef MYCLASS_H_INCLUDED
#define MYCLASS_H_INCLUDED

class MyClass {
public:
    MyClass(const char* s);
    ~MyClass();

private:
    const char* mStr;
};

#endif
// MyClass.cpp

#include "MyClass.h"

MyClass::MyClass(const char* s) : mStr(s)
{
    std::cout << "MyClass(" << mStr << ")" << std::endl;
}

MyClass::~MyClass()
{
    std::cout << "~MyClass(" << mStr << ")" << std::endl;
}

ここで一度、main側のプログラムを書いてみます。まず、MyClassクラスしかないとして、普通に書くと次のようになります。

// main.cpp

#include "MyClass.h"

MyClass* func()
{
    MyClass* p2 = new MyClass("c2");
    MyClass* p3 = new MyClass("c3");

    delete p3;

    return p2;  // p2 を解放するのは呼び出し元
}

int main()
{
    MyClass* p1 = new MyClass("c1");
    MyClass* p2 = func();

    delete p1;
    delete p2;  // main関数内で new しているわけではない p2 の delete を忘れないようにできるか?
}

実行結果:

MyClass(c1)
MyClass(c2)
MyClass(c3)
~MyClass(c3)
~MyClass(c1)
~MyClass(c2)

new をした場所と、delete をする場所とが離れている場合は特にそうなのですが、忘れずに delete を行うことが困難になることがあります。この練習問題の意図は、このような場合でも決して delete を忘れないようにする手を探すことにあります。

そこで、新たなクラスを作成して、delete を行う責任をそのクラスに一任します。そのクラスの名称を MyClassPtrクラスとすると、次のように書けるようにしたいのです。

// main.cpp

#include "MyClass.h"

MyClass* func()
{
    MyClass* p2 = new MyClass("c2");
    MyClassPtr p3(new MyClass("c3")); // delete は MyClassPtr に任せる

    return p2;  // p2 を解放するのは呼び出し元
} // p3 が破棄されるとき、管理下にある MyClass の delete を行う

int main()
{
    MyClassPtr p1(new MyClass("c1"));
    MyClassPtr p2(func());

} // p1、p2 が破棄されるとき、管理下にある MyClass の delete を行う

MyClassPtrクラスのオブジェクトは、new を使わずにインスタンス化していますから、関数を抜け出すときに破棄されます。このとき呼び出されるデストラクタの中で、管理下にある MyClass を delete してやれば、うまく自動化できそうです。

MyClassPtr を定義してみます。

// MyClassPtr.h

#ifndef MYCLASSPTR_H_INCLUDED
#define MYCLASSPTR_H_INCLUDED

class MyClassPtr {
public:
    MyClassPtr(MyClass* ptr);
    ~MyClassPtr();

private:
    MyClass*        mPtr;
};

#endif
// MyClassPtr.cpp

#include "MyClassPtr.h"

MyClassPtr::MyClassPtr(MyClass* ptr) :
    mPtr(ptr)
{
}

MyClassPtr::~MyClassPtr()
{
    delete mPtr;
}

コンストラクタで、MyClass型のポインタを受け取り、メンバ変数^に保持しておきます。あとは、デストラクタで delete してやるだけです。こうしておけば、もはや delete を呼ぶことに気を使う必要はありません。

【上級】まさにこういった目的で使える std::auto_ptrクラステンプレートが、標準ライブラリに用意されています(【標準ライブラリ】第16章)。

ちなみに、デストラクタの実装ですが、本編で登場した SAFE_DELETEマクロを使って次のように書くことは可能ですが、これは少し無駄ではあります。

MyClassPtr::~MyClassPtr()
{
    SAFE_DELETE(mPtr);
}

ここはデストラクタなので、ここから抜け出してしまえば、もはや mPtr にアクセスする手段はありません。そのため、解放の後にヌルポインタで上書きする行為には大して意味がありません。


参考リンク


更新履歴

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

’2014/5/10 新規作成。



第14章のメインページへ

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

Programming Place Plus のトップページへ



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