Help us understand the problem. What is going on with this article?

Boost.Optionalを使って、NULLポインタが呼び出し不能になるようにする

More than 5 years have passed since last update.

IL360 様の投稿記事、NULLでないポインタは実体がなくても呼び出し可能は、大変参考になりました。ありがとうございます。そこで、なんとか回避策を考えてみることにしました。

普通にスマートポインタを使う

とりあえず、普通にスマートポインタを使って、NULL(C++11以降ではnullptr)なポインタを呼び出し不能にできないか試してみます。

#include <iostream>
#include <memory>

struct Test {
    ~Test()
    {
        std::cout << "called destructor" << std::endl;
    }

    void func()
    {
        std::cout << "Hello, World!" << std::endl;
    }
};

int main()
{
    auto pT(std::make_unique<Test>());  // C++14を想定しています
    pT->func();                         // Hello, World!と出力
    pT.reset();                         // ここでTestクラスのデストラクタが呼び出される
    if (!pT)
        // pTは確かにリソースを保持していないのに、、、
        std::cout << "pT doesn't have resource" << std::endl;
    pT->func();                         // ここで、Hello, Worldと出力される…
}

このコードをコンパイルして実行すると、

Hello, World!
called destructor
pT doesn't have resource
Hello, World!

などと表示され、リソースを保持していないスマートポインタに対しても、メンバ関数が呼び出せてしまいます。いろいろ試しましたが、結局これを回避するには、スマートポインタのチェックが必要のようです。

// (クラス定義等は省略)

int main()
{
    auto pT(std::make_unique<Test>());  // C++14を想定しています
    pT->func();                         // Hello, World!と出力
    pT.reset();                         // ここでTestクラスのデストラクタが呼び出される
    if (!pT)
        // pTは確かにリソースを保持していない
        std::cout << "pT doesn't have resource" << std::endl;
    if (pT)
        pT->func();                     // ここには来ない
}

このコードをコンパイルして実行すると、

Hello, World!
called destructor
pT doesn't have resource

となり、期待した動作になります。しかし、いちいちそのスマートポインタがリソースを保持していないかどうかのチェックをするのは非常に面倒ですし、チェック忘れがバグの温床にもなりがちです。

Boost.Optionalを使う

もし「ポインタ」でなくて、「ポインタっぽいもの」で良いのならば、Boost.Optionalが使えます。例えば、

#include <iostream>
#include <boost/optional.hpp>
#include <boost/utility/in_place_factory.hpp>

struct Test {
    ~Test()
    {
        std::cout << "called destructor" << std::endl;
    }

    void func()
    {
        std::cout << "Hello, World!" << std::endl;
    }
};

int main()
{
    boost::optional<Test> pT(boost::in_place());
    pT->func();                                 // Hello, World!と出力
    pT = boost::none;                           // ここでTestクラスのデストラクタが呼び出される
    if (!pT)
        // pTは確かにリソースを保持していない
        std::cout << "pT doesn't have resource" << std::endl;
    pT->func();                                 // ここで実行時エラー
}

このコードをClang 3.5でコンパイルして実行してみると、

Hello, World!
called destructor
pT doesn't have resource
a.out: /home/dc1394/oss/boost_1_57_0/boost/optional/optional.hpp:1002: pointer_type boost::optional::operator->() [T = Test]: Assertion `this->is_initialized()' failed.
中止

と出力され、実行時エラーで止まってくれます(g++ 4.9.2でも試しましたが、同様に止まってくれました)。これは期待した動作です。

ただし、Boost.Optionalは、オブジェクトがフリーストア(ヒープ)に配置されず、スタックに配置されます。さらにポインタと違い、operator=で、オブジェクトがディープコピーされます。

実際、以下のコードをコンパイルして実行してみると、

#include <iostream>
#include <boost/optional.hpp>
#include <boost/utility/in_place_factory.hpp>

struct Test {};

int main()
{
    boost::optional<Test> pT(boost::in_place());
    std::cout << "address of pT  = " << &(*pT) << std::endl;
    auto pT2 = pT;
    std::cout << "address of pT2 = " << &(*pT2) << std::endl;
}

address of pT = 0x7fffe5ed4b91
address of pT2 = 0x7fffe5ed4ba1

となり、オブジェクトがディープコピーされていることが確認できます。
さらに、Boost.Optionalはポリモーフィズムをサポートしません。つまり、

#include <iostream>
#include <boost/optional.hpp>
#include <boost/utility/in_place_factory.hpp>

struct Base {};

struct Derived : Base {};

int main()
{
    boost::optional<Derived> pDeriv(boost::in_place());
    boost::optional<Base> pBase = pDeriv;               // コンパイルエラー
}

このコードはコンパイルエラーとなります。
以上の制限を理解した上で、ポインタの代替としてBoost.Optionalを使うのは、理にかなっていると考えます。

まとめ

C++では、スマートポインタを使っても、リソースを保持していないポインタのメンバにアクセスできてしまいます。
Boost.Optionalは、制約が厳しいですが、ポインタの代替として魅力的だと思います。

dc1394
主にC++とC#を使っています。数値解析が趣味。 博士後期課程中退。専門は第一原理計算。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away