Home / ぼやきごと / 2012-09-26
2012-09-26

C++11:pimplイディオムにおけるデストラクタの default 指定

皆さん、C++11でコーディングしてますか?
私は昨日Fedora9のノートPCに gcc 4.7.2 を手動インストールして、ようやくC++11を書くことのできる環境を手に入れました。

C++11にはたくさんの新仕様がありますが、その中でも私が好きな仕様の一つが default 指定です。
C++のクラスでコンストラクタやデストラクタ等の記述を省略するとコンパイラがそれらを自動生成しますが、その「コンパイラに自動生成させる」ことを明示できる仕様です。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 
-
|
|
|
|
|
|
|
!
class Foo
{
public:
    Foo() = default;
    ~Foo() = default;
    Foo(const Foo&) = default;
    Foo(Foo&&) = default;
    Foo& operator=(const Foo&) = default;
    Foo& operator=(Foo&&) = default;
};

上述のコードにおいて = default の記述がある宣言は書いても書かなくても生成されるクラスは同じです。
しかし書かなかった場合、他の人がコードを読んだ時に「コンパイラに自動生成させようとしている」のか、「単に書き忘れた」のかが区別できません。
敢えて default 指定の宣言を記述することで、「私はこれらをコンパイラに自動生成させています」というアピールになります。
また、他の人だけでなく、コンパイラにもその意図を明示的に伝えることができ、コンパイラによっては最適化の助けになる可能性もあるでしょう。

C++11では、特別な処理をさせる場合を除き、記述を省略したりましてや空っぽの実装を書いたりせずに default 指定を積極的に用いるべきです。
特にデストラクタについては、リソースの解放はメンバ変数に任せてヘッダファイルでの default 指定を義務化するくらいのつもりで考えるべきでしょう。
そしてそれらのメンバ変数を辿った先は、基本型、データ抽象クラス(std::string 等)、各種スマートポインタクラスのいずれかに行き着くようにします。
その辺りの話は次のWebページが参考になります。

ただ、すべてのデストラクタをヘッダファイルで default 指定できるかというと例外があります。

  • デストラクタで(リソース解放以外の)何らかの処理を行いたい場合。
  • リソースそのものを表すデータ抽象クラスやスマートポインタクラスの場合。
  • デストラクタをインライン展開できない場合。

これらのうち、3番目の「デストラクタをインライン展開できない場合」に当てはまるものの1つにpimplイディオムがあります。

詳しくは上のWebページに書かれていますが、pimplイディオムを知っているという前提で簡単に説明します。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
-
!
 
 
 
-
|
|
|
|
|
-
!
|
!
// Var.h
#pragma once
#include <memory>
 
class Var
{
public:
    Var();
    ~Var() = default; // こう書くとコンパイルエラー!
 
private:
    // Var::Impl クラスの完全な定義は別ソースファイルに書く
    class Impl;
    std::unique_ptr<Impl> _impl;
};

上述のコードのようにクラス宣言の中でデストラクタを default 指定で宣言した場合、自動生成されるデストラクタはコンパイラによってインライン展開されます。
するとその場で _impl メンバのデストラクタも呼び出す必要があるため、 std::unique_ptr<Impl> クラスの実体化が試みられますが、 Var::Impl クラスは前方宣言しかされていないために実体化することができず、コンパイルエラーとなるわけです。
これは default 指定の代わりに空のデストラクタを書いても同じことで、解決するためにはデストラクタの実装を Var::Impl クラスの完全な定義が見える場所に記述する必要があります。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
-
!
 
-
!
-
-
!
|
|
|
-
!
// Var.cpp
#include "Var.h"
 
// Var::Impl の定義
class Var::Impl
{
    // …略…
};
 
Var::Var() : _impl(new Impl()) { }
 
// Var のデストラクタの実装を Var::Impl の定義が見える場所に書く
Var::~Var() { }

しかし、 default 指定は何も宣言部でなければ書けないものではありません。
むしろ default 指定は「宣言として書くもの」ではなく、「実装(関数本体)の代わりとして書くもの」と考えた方がいいです。
例えば上述のコードではデストラクタ Var::~Var() の動作は既定のものでよいため、空の実装を書く代わりに default 指定を用いて次のように書くこともできます。

 13
 
Var::~Var() = default;

今回はこの「pimplイディオムを用いたクラスのデストラクタでも default 指定できるよ!」ということだけ言いたかったのですが、何故かやたら長くなってしまいました…。
ともあれ、 default 指定は非常に有用ですので、C++11でコードを書く際には是非活用しましょう。

Category: [プログラミング][C++] - 2012-09-26 00:42:40