C++、ヘッダーの重複(二重)読み込み防止

ヘッダーファイルの重複(二重)読み込みを防止するには、「#include guard(macro guard)と呼ばれる方法」か、「#pragma onceを用いる方法」があります。

ヘッダーファイルの重複読み込み

ヘッダーファイルを重複読み込みしてしまう問題とは、C++ソースファイルをコンパイルする際、ヘッダーファイルAとヘッダーファイルBの二つのファイルを読み込む場合に、先に読み込んだヘッダーファイルAを次に読み込むヘッダーファイルB内で再び読み込んでいると、ヘッダーファイルAの内容が二重定義されてコンパイルエラーになるという問題です。
通常、ヘッダーファイル内で他のヘッダーファイルを読み込むことはよくあるので、ヘッダーファイルの二重読み込みは容易に起こります。それを防ぐために「一度読み込んだヘッダーファイルは二度は読み込まない」という機構を作る必要があります。

二つの解決方法

防止方法は二つあります。「#include guard(macro guard)と呼ばれる方法」と、「#pragma onceを用いる方法」です。
「#include guard(macro guard)」はどのコンパイラでも利用できますが、名前の衝突が生じます。また、コンパイル速度が遅くなるコンパイラもあるそうです。
「#pragma once」は、非標準の方法で、すべてのコンパイラで採用されているわけではありませんが、現在は主なコンパイラで採用されています。こちらは名前の衝突は生じません。

#include guard(macro guard)

#include guardは、読み込まれるヘッダーファイルで「#define ヘッダーファイル名」を定義して、ヘッダーファイル名が定義済みか、定義済みでないかを確認することで、ヘッダーファイルがすでに読み込まれているかを確かめる方法です。つまり、ヘッダーファイル名が定義されていない場合のみ、そのヘッダーファイルが読み込まれていないので、ヘッダーファイルの内容を読み込みます。
具体的には、

#ifndef FILENAME
#define FILENAME
〜ファイルの内容〜
#endif

と書きます。

名前の衝突とは、二つのヘッダーファイルに同じヘッダーファイル名(FILENAME)を付けてしまい、どちらか一方のヘッダーファイルを読み込むと他方のヘッダーファイルを読み込めなくなるという問題です。
問題回避のためには統一した記法を用いることが大切になりますが、異なる制作者間のライブラリを使う場合には問題が生じます。
より詳しくは、ページ末尾の参考サイトをご覧ください。

#pragma once

#pragma onceデイレクテイブは、読み込まれるヘッダーファイルに、
#pragma once
と記述するだけで、そのヘッダーファイルの二重読み込みを防止してくれます。従って、名前の衝突も生じません。
gccはバージョン3.4からサポートしているらしく、私の使っているコンパイラgcc バージョン 4.8.2では、確かに使うことができました。
#pragma onceが使えるコンパイラ一覧は、以下の参考サイトをご覧ください。

参考サイト

Include guard, Wikipedia
http://en.wikipedia.org/wiki/Pragma_once, Wikipedia

最終修正日:2016/10/6

C++ デフォルトコンストラクタの自動生成と暗黙的な呼び出し

C++のデフォルトコンストラクタについて整理します。デフォルトコンストラクタの自動生成、自動生成されない場合、暗黙的に呼び出される場合を取り上げます。補足的にオブジェクトの初期化式やクラスメンバーの初期化リストの書き方も取り上げます。

デフォルトコンストラクタの自動生成

C++ではデフォルトコンストラクタは、コンストラクタを書かなければ自動生成されます。しかし、他のコンストラクタを一つでも書くと自動生成されません。そのため、他のコンストラクタを書き、うっかりデフォルトコンストラクタを書き忘れると下記の式はコンパイルすることができません。

ClassName VariableName = ObjectName;

この式は、左辺においてオブジェクトがデフォルトコンストラクタにより初期化・生成された後に、Operator=()で右辺のオブジェクトを左辺のオブジェクトに代入(コピー)することになります(参照:C++ クラスの代入演算子 代入に必要なコンストラクタ)。そのため、デフォルトコンストラクタがなければこの式はコンパイルエラーになります。

クラスの定義の際に書かなくても自動生成されるのは、デフォルトコンストラクタだけではなく、デフォルトデストラクタ、デフォルトコピーコンストラクタ、デフォルト代入演算子関数も自動生成されます。

デフォルトコンストラクタが暗黙的に呼び出される場合

上記の式では、左辺でデフォルトコンストラクタが暗黙的に呼び出されました。その他にも以下に列挙するようにデフォルトコンストラクタが暗黙的に呼び出される場合があるので気を付ける必要があります。

クラスの継承の場合で、スーパークラスのコンストラクタを初期化リストで明示的に呼び出さない場合には、デフォルトコンストラクタが暗黙的に呼び出されます。そのため、スーパークラスに引数付きのコンストラクタをオーバーロードして、デフォルトコンストラクタがなくなった場合は、サブクラスの初期化リストで明示的にスーパークラスに実装した引数付きコンストラクタを呼び出さなければ、エラーとなります。

クラスのメンバについても初期化リストで明示的にコンストラクタを呼び出さない場合には、デフォルトコンストラクタが暗黙的に呼び出されます。そのため、引数付きコンストラクタをオーバーロードしたクラスを、他のクラスのメンバにした場合に、初期化リストで実装したコンストラクタで明示的に初期化しなければコンパイルエラーになります。
ちなみに、初期化リストでメンバを初期化しないでコンストラクタ内で初期値を設定するということは、デフォルトコンストラクタで初期化された後に、コンストラクタ内で代入処理をすることであり効率が落ちます。これは最初に挙げた式、

ClassName VariableName = ObjectName;

のような二段階の処理になります。また、初期化の順序はスーパークラスのコンストラクトを始めにして順番に初期化すると問題が生じにくくなります。基本データ型の場合には、関数内の自動変数と同じく初期化しなければ適当な値が入ります。

オブジェクトの初期化式やクラスメンバーの初期化リストの書き方

C++のオブジェクトの初期化式は、引数がない場合と引数がある場合によって、

ClassName VariableName;
ClassName VariableName(AnyArguments);

と「()」の有無が異なる形式で書きます。
問題は、引数がないのに、

ClassName VariableName();

と書くと仮引数のないVariableName関数のプロトタイプ宣言とみなされてしまいバグになることです。
引数がない場合は括弧を取って、

ClassName VariableName;

と宣言すれば、デフォルトコンストラクタが正しく呼び出され初期化されます。初期値のない基本データ型の宣言と同じと考えれば良いと思います。
初期値のある場合の基本データ型の初期化は通常「=」で行いますが、引数のあるオブジェクトの初期化式と同様に「()」を用いて初期化することもできます。

int a = 0;
int a(0); //こちらもOK

クラスの基本データ型のメンバの初期化は、初期化リストでこの「()」を用いた形式を使って行います。以上のようにC++では基本データ型とクラスオブジェクト型で初期化形式の整合性が図られているようです。

参考サイト

#pragma twice 338 Version 16.11 様々なコンストラクタ, #pragma twice, 1999-2007
第16章 派生と構築, ロベール, 2001
代入より初期化, C++とプログラム全般
初期化指定子, ソフトウェア インフォーメイション センター IBM

C++のconst宣言の対象、効果、効果範囲、その他の注意点

const宣言は、宣言されたデータを変更できないデータ、つまり定数にします。
C++のconst宣言の対象、効果、効果範囲、その他注意点について整理したいと思います。

const宣言の対象データ

まず、const宣言が付けられるデータ型は何かを整理したいと思います。

C++のデータ型

C++はデータの取扱方法として、
->ポインタを通して扱う
->参照を通して扱う
->データ実体(レコード)を直接扱う
の3通りの方法を持っています。
そして、データ実体には、基本データ型、データセット(クラス、構造体)、配列、共用体、enum、関数(メソッド)などがあります。

const宣言の対象となるデータ、ならないデータ

const宣言は、変更できるデータを変更できないデータにするため、そもそも変更できないデータは対象になりません。

変更できないデータ:
->関数(ただし、メソッドにはオブジェクトの変更不可という目的でconst宣言が付く。)
->参照自身(参照自身は変更できない。参照先のデータ実体については変更できる。)

変更できるデータ:
->ポインタ自身
->ポインタ先のデータ実体
->参照先のデータ実体
->データ実体
->->基本データ型
->->データセット
->->その他

以上の変更できるデータがconst宣言の対象になります。
ちなみに、参照自身が変更できないことについての詳解は、C++の参照の宣言方法、振る舞い、一時オブジェクトによる初期化をご覧ください。

const宣言の効果と効果範囲

次に、const宣言の効果と効果範囲を対象データごとに考えます。

ポインタ自身

DataName * const VariableName = AnyPointer;

ポインタは、ポイントするレコードのアドレスであり、アドレスVariableNameの前にconstを付けるとアドレスVariableName自身が変更できなくなります。この宣言の場合、ポイント先のデータ実体は変更できます。

ポインタ先のデータ実体

const DataName *VariableName = AnyPointer;

デリファレンスされたDataName型のレコードにconstが付けられています。よって、ポイント先のデータ実体をこのポインタを通して変更できなくなります。この宣言の場合、ポインタそのものは変更できます。後ほど詳解しますが、このポインタを通さなければデータ実体の変更はできます。

参照先のデータ実体

const DataName &VariableName = AnyData;

この参照を通してデータ実体を変更できなくなります。次に詳解しますが、この参照を通さなければデータ実体の変更はできます。

ポインタ先のデータ実体と参照先のデータ実体の補足

ポインタ先のデータ実体と参照先のデータ実体の注意点として、const宣言したポインタや参照を通さなければデータ実体を変更することはできて、それがconst宣言されたポインタや参照にも反映されます。
例:

*AnyPointer = AnyData1;
const DataName *VariableName = AnyPointer;
*AnyPointer = AnyData2;
if(*VariableName == AnyData2) //true

つまり、const宣言したポインタや参照を通してデータ実体を変更することはできませんが、データ実体それ自体は定数にはならず変更できます。さらに、ポインタや参照はコピーしたデータ実体をポイント・参照しているのではなく、直接データ実体をポイント・参照しているのでデータ実体の変更がポインタや参照にも反映されます。
「ポイント先のデータ実体が定数になる」、「参照先のデータ実体が定数になる」ではなく、「ポインタを通してデータ実体を変更できなくなる」、「参照を通してデータ実体を変更できなくなる」ということです。

基本データ型

const DataName VariableName = Data;

データ実体VariableNameを変更できなくなります。ちなみに、VariableNameはDataと異なるデータとして初期化されるので、後からDataを変更してもVariableNameに変更が反映されることはありません。

データセット

const ClassName ObjectName;
const ClassName ObjectName(AnyArguments);

データ実体ObjectNameを変更できなくなります。データセット(クラス、構造体)は、メンバで構成されるデータ集合なので実際にはデータ実体である各メンバを変更できなくなります。メンバにはポインタ、参照、データ実体が入りますが、ObjectNameのデータ実体である
->ポインタ自身
->参照自身(そもそも変更できないデータ)
->データ実体
が変更できなくなります。

一方で、
->ポインタ先のデータ実体
->参照先のデータ実体
は変更できます。(このポインタや参照を通してデータ実体を変更することができます。)

さらに、オブジェクトメンバにオブジェクトがあるような場合は、上記のルールがデータ実体について再帰的にあてはまります。

メソッドのconst宣言

class ClassName{
 DataName Method() const {}
};

1. Methodを通してClassNameのデータ実体を変更できなくなります。
2. ClassNameのデータ実体のポインタや参照をconstなしに返すことができなくなります。
3. Methodからconstが付いていない他のメソッドを呼び出せなくなります。
一方で、ClassNameのオブジェクトをconstで作成した場合に、constが付いていない他のメソッドは呼び出せませんが、const宣言したMethodは呼び出せるという効果が生じます。これにより安全なクラス設計を行えます。

以上、const宣言の効果と効果範囲を考えてきました。

その他の注意点

初期化なしのconst宣言はできない

初期化なしのconst宣言はできません。
基本データ型の例:

const DataName VariableName = Data; //OK
const DataName VariableName; //エラー

const宣言を緩めることはできない

ポインタや参照を使って、変更できないデータを変更できるデータに制限を緩めることはできません。
例:

const DataName VariableName = Data;
DataName *PointerName = &VariableName; //エラー
const DataName *PointerName = &VariableName; //OK

ちなみに、=でデータを代入する場合や、関数の引数・返値でデータを引き渡す場合に、データを「コピー」することはできます。

一時オブジェクトによる参照の初期化

通常、一時オブジェクトで参照の初期化をすることはできませんが、const宣言を付することで一時オブジェクトでも参照を初期化することができます。

int &VariableName = 123; //エラー
const int &VariableName = 123; //OK

詳しくは、C++の参照の宣言方法、振る舞い、一時オブジェクトによる初期化をご覧ください。

参考サイト

その2 constのあれこれ, ○×(まるぺけ)つくろーどっとコム, since 2005/5/13
その17 constのあれこれ2, ○×(まるぺけ)つくろーどっとコム, since 2005/5/13
C++の基礎 : const 修飾子, ソフィア・クレイドル, 2002-2014

C++の参照の宣言方法、振る舞い、一時オブジェクトによる初期化

C++の参照の宣言方法、振る舞い、一時オブジェクトによる初期化について整理したいと思います。

参照の宣言

C++の参照は、以下のように宣言します。
TypeName &AnotherName = ObjectName;

「&」演算子は、式中で変数の前に置くとそのレコードのアドレス(いわゆるポインタ)を返しますが、初期化式で使うと参照変数またはエイリアスの宣言とみなされます。エイリアスとは別名という意味です。C++の参照は「別名」なので以下のような初期化なしでの参照変数の宣言はできません。

ClassName &AnotherName; //エラー
AnotherName = ObjectName;

参照の振る舞い

参照変数は、初期化後は参照しているオブジェクトと「同様」に振る舞うので、

AnotherName = TheOtherObjectName;

上記の式はAnotherNameが参照しているObjectNameへのTheOtherObjectNameのコピーを行います。TheOtherObjectNameをその参照変数に置き換えても同じです。したがって、C++では一度宣言した参照変数の参照先を変更する方法はありません。別の言い方をすると参照変数の参照のみを入れ替えるということはできません。そのため、C++では一度宣言した参照変数の参照先は不変です。変数にオブジェクトの参照が入り、変数の参照先を入れ替えることができるJavaやJavaScriptに慣れた方は初めは違和感を覚えるかもしれません。

一時オブジェクトによる初期化

下記の一時オブジェクトによる参照変数の初期化はエラーになります。

int &VariableName = 123; //エラー

参照型は参照のための領域を確保するだけなので、参照すべき123はどこにも確保されず初期化後に消えてしまいます。

一方で例外的に、

const int &VariableName = 123;

は成立します。通常、初期化後に削除される一時オブジェクトが、const宣言された参照を初期化する場合には例外的に存続するためです。ちなみに、どうしてそのような例外的な処理をするのかですが、私は一応以下のように理解しています。

const宣言付きの一時オブジェクトによる参照の初期化

const宣言なしの場合にコンパイルエラーになる理由

const宣言なしの場合には、参照を通して参照元を変更することができます。つまり、参照を通してなされた変更が参照元に反映されることになります。もしも、右辺の参照元がなくなっていたり、参照先が右辺のオブジェクトとは異なるオブジェクトになってしまった場合には、その変更は参照元に正しく反映されないことになります。このようなことが起これば参照は不安定な機構になりますし、そもそも参照とは言えなくなります。そのためコンパイルエラーになります。
ここで、右辺の参照元がなくなっている場合というのは、上述もした

int &VariableName = 123; //エラー

のような例です。
参照先が右辺のオブジェクトとは異なるオブジェクトになってしまった場合というのは、参照先となるはずの右辺のオブジェクトから型変換されて生じた一時オブジェクトにより、参照が初期化される場合です。作成された一時オブジェクトを参照してしまっては、参照をしているつもりのオブジェクトへの変更が反映されず混乱が生じます。そのためコンパイルエラーになります。
ちなみに、C++には参照変数の初期化において左辺の参照変数の型が、右辺の参照元のオブジェクトの型をアップキャストしてできる場合には初期化が成立し、その右辺のオブジェクトを直接参照し続けるという仕様があります。一方で、アップキャストができない場合には、型変換された一時オブジェクトを参照するということはせず、前述のとおりコンパイルエラーになります。あくまでも右辺値を参照元とできるかどうかで成否を分け、参照における混乱を生じさせないようになっているようです。
以上のことから、const宣言なしの場合には一時オブジェクトによる参照変数の初期化はできません。

const宣言ありの場合に成立する理由

一方でconst宣言ありの場合は一時オブジェクトによる参照変数の初期化ができます。参照変数の初期化にconst宣言が付けられると、参照を通して参照元を変更することができなくなります。そうすると、たとえ一時オブジェクトを参照元としても、const宣言なしの場合に問題になった参照を通してなされた変更が参照元に反映されないという問題が、そもそも変更を加えることができないので問題ではなくなります。このため、const宣言ありの場合は一時オブジェクトによる参照変数の初期化が成立するのです。
さらに、一時オブジェクトを参照できることで、アップキャストできる場合だけではなく、型変換や式の評価の結果を柔軟にconst宣言の参照元として扱えるようになります。
ちなみに、const宣言を付けて参照変数を初期化した場合に、参照元が右辺のオブジェクトかあるいは型変換や式の評価によって生じた一時オブジェクトかによって生じる唯一の違いは、右辺のオブジェクトを参照を通さずに変更した場合に、その変更がconstである参照にも反映されるかされないかということです。右辺のオブジェクトへの直接的な参照であれば、変更は反映されます。それはC++の参照のconst宣言の効果が、「参照を通しての変更ができない」であって「参照元を変更できない」ではないからです。一方、型変換や式の評価によって生じた一時オブジェクトを参照している場合には、別のオブジェクトである右辺のオブジェクトへの変更は、参照には反映されないことになります。

参照のconst宣言の効果についてはC++のconst宣言の対象、効果、効果範囲、その他の注意点をご覧ください。

参考サイト

C++編(言語解説) 第15章 参照, Programing Place, 2012/1/25
一時オブジェクト,ソフトウェア インフォーメイション センター IBM
参照の初期化 (C++ のみ),ソフトウェア インフォーメイション センター IBM

C++の代入演算子とコピーコンストラクタ

C++の代入演算子とコピーコンストラクタについて注意事項を整理しました。さらに、基本データ型との比較によって代入演算子とコピーコンストラクタについての理解を深めます。

代入演算子

C++で以下のような代入演算を行うと、どうなるでしょうか。

ClassName VariableName; //初期化
VariableName = ObjectName; //代入演算

JavaやJavaScriptであれば、オブジェクトは参照として変数に代入されるので、ObjectNameが指すオブジェクトをVariableNameでも使えるようにするために、上記のような代入演算をよく用いると思います。一方、C++の場合は、上記式の処理は「代入演算子Operator=()が実行され、VariableNameオブジェクトにObjectNameオブジェクトの内容がコピーされる。」ことになり、実際は代入演算子というよりコピー演算子と言えます。つまり、JavaやJavaScriptでは実体の同じオブジェクトの参照を変数間でコピーするのに対して、C++ではVariableNameオブジェクトとObjectNameオブジェクトの実体は異なっていて、その内容がコピーされます。
JavaやJavaScriptのプログラミングに慣れた方は、参照を扱っているつもりで実体の異なるオブジェクトを操作してしまうことになるので気を付ける必要があります。さらに、代入演算子Operator=()はデフォルトで定義されていますが、あくまでもレコードのコピーであり、メンバにポインタがあってもポインタをコピーするだけで、その新しいオブジェクトを作成してくれるわけではありません。この点にも気を付ける必要があります。ちなみに、代入演算子Operator=()は下記のような宣言になります。

void Operator=(const ClassName &copy);

C++でオブジェクトの実体を一意に扱いたい場合は、ポインタを使います。「&」演算子で参照を宣言することもできますが、JavaやJavaScriptのように変数内の参照を入れ替えることはできません。参照については、C++の参照の宣言方法、振る舞い、一時オブジェクトによる初期化をご覧ください。

コピーコンストラクタ

ちなみに、冒頭の式について初期化と代入演算を一度に行う場合は、

ClassName VariableName = ObjectName;
ClassName VariableName(ObjectName);

とします。どちらも同じ処理で、コピーコンストラクタが呼び出されます。コピーコンストラクタは、

ClassName(const ClassName &copy)

と宣言します。こちらもデフォルトで定義されていますが、あくまでもレコードのコピーであることは、代入演算子と同じで気を付ける必要があります。

基本データ型との比較

定義型と基本データ型の初期化・代入の形式を比較してみます。
定義型:

ClassName VariableName; //引数なしの初期化
VariableName = ObjectName; //代入演算
ClassName VariableName = ObjectName; //引数が同じクラスでの初期化、コピーコンストラクタ
ClassName VariableName(ObjectName); //引数が同じクラスでの初期化、コピーコンストラクタ
ClassName VariableName(AnyArguments); //その他の引数での初期化

基本データ型:

DataName VariableName; //Data(リテラル式、他の変数)なしの初期化
VariableName = Data;//代入演算
DataName VariableName = Data; //Dataでの初期化
DataName VariableName(Data); //Dataでの初期化

比較すると、代入演算子とコピーコンストラクタの役割がはっきりすると思います。そもそもC,C++の基本データ型に対する=演算子の効果はデータのコピーであり、これは定義型に関しても変わらないということです。

C++で正規表現を使う、FedoraでBoostをインストール

C++で正規表現を使う方法には、標準ライブラリを用いるか、Boostを用いるかの二つの方法があります。
以下の説明では、LinuxディストリビューションはFedora19を、コンパイラはGNU gcc 4.8.2を使っています。

標準ライブラリ

まず、C++の最新の標準ライブラリに入っているstd::regexを試しました。

#include <regex>

でインクルードできます。
GNU gcc 4.8.2の場合、まだ実験版なのでコンパイルオプション「-std=c++11」が必要です。
試してみると、正規表現にメタ文字を入れてコンパイルした場合に、コンストラクタがエラーを出します。
実験版のためと考えられるので、今回はstd::regexの使用を諦めました。

Boostのインストール

そこで、boost::regexを利用しました。
Fedoraの場合、boostはyumパッケージで配布されています。

yum install boost boost-devel

で使えるようになります。

注意事項:

yum install boost

でboostパッケージだけをインストールしても、ヘッダーファイルはインストールされません。開発版boost-develのインストールが必要です。開発版boost-develのインストール後に、以下のコマンドでヘッダーが入っていることを確認できます。

find /usr/include/ -name "*boost*"

なお、以下のコマンドで共有ライブラリが入っているかを確認できます。

find /usr/lib/ -name "*boost*"

boost::regexのコンパイル

boost::regexのインクルードは、

#include <boost/regex.hpp>

で行います。
インクルードのためのコンパイルオプションは、デフォルトで/usr/include/にパスが通っているので必要ありません。パスが通っていない場合は、-Iオプション(大文字のアイ)や環境変数CPLUS_INCLUDE_PATHの設定が必要です。

boost::regexを使用するには、ヘッダーファイルのインクルードに加えて共有ライブラリのリンクが必要です。
共有ライブラリのリンクのためのコンパイルオプションは、-Lオプションで探索パスを設定し、-lオプション(小文字のエル)でライブラリを指定します。
boost::regexの場合には、探索パスについては/usr/lib/にパスが通っているので必要ありません。パスが通っていない場合には、-L/usr/lib/とオプション指定してください。
ライブラリの指定については「-lboost_regex」オプションでできます。
したがって、

g++ -lboost_regex filename.cpp

でコンパイルすることができます。
注意:上記、「-lboost_regex」の「_」が不鮮明なブラウザがありますので、ご注意ください。
共有ライブラリは、-lライブラリ名で指定します。ファイル名にはlib接頭辞が付き、libboost_regex.soがリンクされます。.soは共有ライブラリの拡張子です。共有ライブラリについて、詳しくは下記の参考サイトを参照してください。

参考サイト

Are the boost headers separately packaged under Fedora?, Unix & Linux Stack Exchange, 2012
letsboost::インストール, Let’s Boost, Kazuhiro Inaba
第 16 章 ライブラリの構築, Sun Studio 12: C++ ユーザーズガイド, ORACLE, 2010年
Linux 動的ライブラリーの徹底調査, M. Tim Jones, IBM Developer Works, 2008年8月20日
Linux共有ライブラリの簡単なまとめ, wagavulinの日記, 2009年10月26日