Boost.Asio リンク集

id:faith_and_brave さんが最近 Boost.Asio 関連の記事をたくさん投稿してて,どこかにメモっておきたかったのでここにメモ.それ以外のサイトの記事も見つけたら,ここに一緒に追加していこうと思います.

Visutal Studio で Boost C++ Libraries をビルドする

Visutal Studio で Boost C++ Libraries をビルドする必要が出てきたのですが,いくつか嵌った事があったのでメモ.まず,以下のサイトで Windows のバイナリが配布されているようです.バージョンは,3/23 現在で 1.44.0 とやや古い(最新バージョンは 1.46.1)ですが,手軽にインストールするならこれを利用する方法もあります.

今回は,バージョンが古いのと x64 バイナリを作成するためのライブラリも必要だったので自力でビルドする事にしました.

bjam オプション

Boost のビルド自体は,解凍したディレクトリで以下のコマンドを叩けば,基本的には OK です.

C:\code\boost_1_46_1> bootstrap.bat
C:\code\boost_1_46_1> bjam.exe 

ここでは,bjam.exe --help で出てくるもののうち,よく使いそうなものを以下に抜粋しておきます.「--(二重ハイフン)」があったりなかったりするので注意が必要です.特に toolset の箇所は,(ググると)古い情報のものだと --toolset=<value> と指定するようにと言う説明の記事もあるので特に注意する必要があります.










オプション 説明
--stagedir=<value> *.lib がインストールされるディレクトリ.デフォルト値は ./stage*1
--without-<value> ビルドしないライブラリを(その個数分)指定する.ver. 1.46.1 では,以下の 17種類が存在する.

  • date_time
  • filesystem
  • graph
  • graph_parallel
  • iostreams
  • math
  • mpi
  • program_options
  • python
  • random
  • regex
  • serialization
  • signals
  • system
  • test
  • thread
  • wave

デフォルトでは,全てのライブラリがビルドされる.

toolset=<value> ビルドに使用するツールセット(コンパイラ&リンカ?)を指定する.Visual Studio の場合は msvc.尚,msvc-9.0 のようにバージョンを併記すると,(複数の Visual Studio が存在する場合に)使用するツールセットを厳密に指定できる模様.
variant=<value> debug or release のどちらかを指定する.debug,release と記述すると両方のライブラリが同時に生成される?
link=<value> static or shared のどちらかを指定する.Boost のライブラリを静的リンクするかどうかを指定する.
runtime-link=<value> static or shared のどちらかを指定する.こちらは,VC++ の標準ライブラリを静的にリンクするかどうかを指定する.プロジェクトで /MT を指定する場合には runtime-link=static, /MD を指定する場合には runtime-link=shared にしないとライブラリのリンクで問題が発生する模様.

デフォルト(bjam.exe のみ)でビルドすると runtime-link=shared でビルドされるようで,/MT 指定のプロジェクトでビルドしようとすると Boost のライブラリをリンクする際にエラーが発生しました.したがって,/MT でビルドする場合は runtime-link=static を明示的に指定する必要があるようです.

x64 版のビルド

引き続き x64版のビルド.まず,私は x64 版のコマンドラインを出現させるところで躓いたのでその準備から・・・ Express バージョンでは恐らく,x64 用のコマンドプロンプト(のためのショートカット)は作成されないようです.そのため,まずスタートメニューの「Visual Studio コマンドプロンプト」をコピーし,「リンク先」の箇所の引数の部分を 「x86」から「amd64」に変更して x64 用のコマンドプロンプトを作成します.

%comspec% /k ""C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"" x86
↓
%comspec% /k ""C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"" amd64

しかし,これだけでは起動に失敗します.amd64 の場合は VC\bin\amd64\vcvars64.bat のバッチファイルを利用するように記述されてあるのですが,該当のファイルが存在しないためです.そこで,以下のような記述をした vcvars64.bat ファイルを作成し,該当ディレクトリにコピーします.尚,これを実行するためには,あらかじめ Windows SDK をインストールしておく必要があります(下記の例だと Windows SDK 7.1).

@echo off
CALL "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64

この後,表示されたコマンドプロンプト上で以下のように address-model=64 を付けてビルドすると,x64 用のライブラリがビルドされるようです.

C:\code\boost_1_46_1> bjam.exe toolset=msvc address-model=64 link=static runtime-link=static

最初に x64用のコマンドプロンプトを起動しないと toolset=msvc-10.0.amd64 とやっても address-model=64 とやっても x86用のライブラリが作成されてしまうようで,結構長い時間ここで嵌りました.

*1:正確には,指定したディレクトリ下に lib ディレクトリが作成され,その中に *.lib がインストールされる.

継承とテンプレート

この記事は,C++プログラマであるかを見分ける10の質問 - Life like a clown の「抽象クラスとテンプレートクラスの使い分けについてインターフェースと言う観点から述べよ」に対する回答的な記事です.

インターフェース的な基底クラスは,テンプレートで代替できる場合があります.例えば,

class interface {
public:
    virtual ~interface() {}
    virtual void do_something() = 0;
};

void func(interface& x) {
    x.do_something();
}

と言うインターフェース(的な)クラスを引数に取る関数 func() は,以下のように書く事でも実現できます.

template <class T>
void func(T& x) {
    x.do_something();
}

さて.この 2種類をどのように使い分けるかについては私自身もはっきりとした答えを持っていませんが,個人的には「可能ならばできるだけテンプレートの形で書く」と言う方針を取っています.その理由は,ユーザに class implemented : public interface と書かせるのがめんどくさいと言う切実な理由もありますが,その他にキャストは可能な限り発生させたくないと言うものがあります.virtual 関連の記事(overload と override と hiding - Life like a clown, virtual デストラクタ - Life like a clown)でも述べましたが,基底クラスのポインタから派生クラスのメンバ関数(メソッド)を実行する場合 virtual の有無で挙動が変わる事があり,プログラマの不注意などで予期せぬ不都合を発生させる事があります.そのため,アップキャストであっても発生させなくて良いのであればできるだけ発生させないと言うのが個人的な方針です.

継承とテンプレートの使用方法に関する別の比較例としては,インターフェース的なものを定義したときのその実装の仕方があります.例えば,上記の Interface を実装する場合,継承と言う手段を用いる場合は以下のようなコードになります.

class implemented : public interface {
public:
    virtual ~implemented() {}
    
    virtual void do_something() {
        std::cout << "implement do_something()" << std::endl;
    }
};

int main() {
    implemented x;
    x.do_something();
    return 0;
}

これをテンプレートを用いて代替する場合,以下のような書き方が考えられます.

class policy_implemented {
public:
    void exec() {
        std::cout << "implement do_something()" << std::endl;
    }
};

template <class DoSomethingPolicy>
class implemented {
    DoSomethingPolicy policy_;
public:
    explicit implemented(const DoSomethingPolicy& policy = DoSomethingPolicy()) :
        policy_(policy) {}
    
    void do_something() {
        policy_.exec();
    }
};

int main() {
    implemented<policy_implemented> x;
    x.do_something();
    return 0;
}

継承する代わりに,インターフェース的なクラス(のインスタンス)をメンバ変数に持たせて実際の処理をそのクラスに移譲すると言う戦略が考えられます.これは,デザインパターンで言うところの TemplateMethodStrategy の対比と考える事ができます.したがって,使い分けに関しても「TemplateMethod と Strategy をどう使い分けるか」と言う観点で考える事もできます.

継承とテンプレートの合わせ技

インターフェース的な使い方をする場合,継承とテンプレートとで代替可能な場面が多々あるのですが,中には(普通にやると)継承じゃないと(正確には,きちんとインターフェース的なクラスを定義していなければ)実現できないケースもあります.この際に,ユーザからこの継承部分を隠蔽する方法が提案されました.この方法は Type Erasure と呼ばれています.

非テンプレート基本クラスとテンプレート派生クラスによる Type Erasure では,その名のとおり,リスト 7-15 のような非テンプレート基本クラス (base) とテンプレート派生クラス (derived) を用意しておきます.

// リスト 7-15
class base {
public:
    virtual ~base() {}
};

template <class T>
class derived : public base {
    T value_;
public:
    derived(const T& value) : value_(value) {}
    virtual ~derived() {}
};

これを用意しておくと,継承の特性を利用して derived が持つ型情報 T を base に隠すことができ,メンバ関数テンプレートで受け取ったパラメータをメンバとして保持できます(リスト 7-16).

// リスト 7-16
class X {
    base* p_;
public:
    template <class T>
    X(const T& value) {
        p_ = new derived<T>(value);
    }
    
    ~X() { delete p_; }
};
C++テンプレートテクニック

Type Erasure については,静的型付け言語におけるダックタイピング (Type Erasure) - Life like a clownhttp://d.hatena.ne.jp/tt_clown/20101212/1292132302 辺りでも触れていますので,参照下さい.

コンストラクタ,デストラクタでの例外処理

この記事は,C++プログラマであるかを見分ける10の質問 - Life like a clown の「コンストラクタ,デストラクタにおける例外処理についての戦略を述べよ」に対する回答的な記事になります.例外処理は通常においてもなかなか難しい処理の一つですが,コンストラクタ,デストラクタにおける例外処理をどうするかは,さらに頭を悩ませる問題となります.

コンストラクタ

コンストラクタにおいて例外を発生させる場合にはデストラクタが実行されないため,気をつける事は「(デストラクタで行われるはずであった)各種リソースの開放を例外を送出する前にきちんとを行う」になります.コンストラクタで例外を発生させるべきではない派の主張の論拠も大体ここに起因するようです.

比較的有名なサイトで「コンストラクタからの例外送出」が「禁じ手」として紹介されていることもあり、また、最近ではその内容を再編集した書籍が出版されたこともあって、コンストラクタから例外を送出すべきではないと考える人は多いようです。

その根拠となっているのは、コンストラクタから例外を送出した場合、デストラクタが呼ばれないためにリソースリークにつながるというものです。

[迷信] コンストラクタから例外を送出してはならない | 株式会社きじねこ

コンストラクタで例外が発生した場合,そのクラス自体のデストラクタは実行されませんが,(初期化の終了した)メンバ変数のデストラクタは実行されます.したがって,例えば new でメモリを確保するようなメンバ変数の場合は,スマート・ポインタで保持しておく事によってその部分のリソース開放が自動的に行われ,リソースの開放忘れのかなりの部分を防ぐ事ができます.

尚,メンバ変数の初期化中に送出された例外は以下(outer クラスのコンストラクタ部分)のように記述することによって catch できます (function-try-block).

#include <iostream>

class inner {
public:
   inner(int n) { throw n; } // ここで例外
};

class outer {
   inner in;
public:
   outer(int n) try : in(n) {
       std::cout << "~outer()" << std::endl;
   } catch ( int err ) {
       // 例外を捕まえる
       std::cout << "caught at outer's ctor" << std::endl;
   }

   ~outer() {
       std::cout << "~outer()" << std::endl;
   }
};
http://ml.tietew.jp/cppll/cppll/thread_articles/9439

デストラクタ

一方,デストラクタにおいては,「デストラクタでは例外を送出しない」と言う方針を取る事がほとんどです.

  • C++ の仕様は、コンストラクタやデストラクタが例外を投げてはいけないとは書いていないし、「注意事項」さえ守れば投げてもいい。実際、コンストラクタが例外を投げるのはそれほど悪いことではない。
  • 一方でデストラクタが例外を投げるようなコードは大抵良くない。何故かというと、上記「注意事項」の一つであるところの「例外によるスタック巻き戻し中に別の例外を投げてはいけない(いわゆるダブルフォルトの禁止)」を抵触しないようにプログラムを書くのが難しくなるから。

・・・(中略)・・・

  • これと比較して、デストラクタで例外を投げた場合、少なくともクラス実装だけでダブルフォルトを防ぐことは不可能だし、そもそもデストラクタで例外を投げさえしなければ、まず絶対にダブルフォルトは起こらない。もちろん、デストラクタが例外を投げたとしてもダブルフォルトが起こらないようにプログラムすることは不可能ではないのだが、デストラクタが例外を投げてしまうばっかりに、覚えなければならない概念(=ダブルフォルト)が増えてしまう。これは、投げることによる利益と比較すると大きな代償だ。だから、デストラクタは例外を投げちゃいけない。
http://diary.imou.to/~AoiMoe/2007.12/early.html

尚,デストラクタでは例外を送出しない事を明示化するために例外指定 throw() を書く事がありますが,例外指定は C++0x で deprecate になったようです.そのため,今後の互換性を考えると書くかどうかは悩むところです.

例外指定をdeprecateにする。例外指定は、現実では、まったく意味がなかった。例えば、テンプレートが絡むと、明示的に投げる可能性のある例外を指定するなどということは、不可能になってしまう。

ただし、いくつかのコンパイラでは、何も指定しない、throw()を、関数は例外を投げないという指定とみなして、そのような最適化をしている。そこで、noexceptというキーワードを、新たに導入して、「この関数は例外を投げないと保証できる」と宣言出来るようにした。

本の虫: post-Pittsburgh mailingの簡易レビュー

virtual デストラクタ

この記事は,C++プログラマであるかを見分ける10の質問 - Life like a clown の「virtual デストラクタの概要および使用上の戦略について述べよ」に対する回答的な記事です.

virtual デストラクタの役割は,派生クラスのデストラクタも適切に実行されるようにすると言うものです.virtual の付いていないデストラクタを持つクラスを継承して new でインスタンスを生成し,その継承したクラスへのポインタを基底クラスで保持してしまうと,delete の際に基底クラスのデストラクタのみが実行されてしまうので,派生クラスのデストラクタが実行されずリソースの開放漏れなどの問題が発生します.

virtual デストラクタに関しては,以下の記事で話題になったので一度流れを追ってみるのが良いと思います.

さて. virtual デストラクタに関する戦略ですが,簡単に言うと「インターフェース的な使われ方をするクラス(継承される可能性があり,かつ派生クラスのインスタンスを基底クラスのポインタで保持する可能性がある)の場合にはデストラクタに virtual を付ける」と言うものになります.

多くの場合、仮想デストラクタに関しては、

  • ポリモルフィズムをするための基底クラスには仮想デストラクタを持たせよう。特に仮想関数を持つクラスは必ず仮想デストラクタを持たせよう。
  • 継承を目的としていないクラス、または継承を目的としていてもそれがポリモルフィズムを目的としないのならば仮想デストラクタを宣言すべきではない。

となると思います。

ある程度経験を積んだC++プログラマは絶対にvirtualデストラクタのないクラスを継承しない? - 神様なんて信じない僕らのために

ここで,問題になるのが「あるクラスがインターフェース的な使われ方をするかどうかは分からない(インターフェース的な使い方をされないと言う保証ができない)」と言うものです.この辺は各人のクラス設計の問題にもなってくるのですが,「取りあえず全部に virtual を付けよう」派の主な論拠もこの辺りに起因しそうです.

個人的には,ある程度利用範囲の限られるクラス(あるプロジェクト内でのみ使われるとか)で今後どんな追加的な修正が入るか予想できないような場合には,「取りあえず全部のデストラクタに virtual を付けておく」と言う選択肢を取るのも良いと思います.ただし,これがいきすぎて「全てのクラスのデストラクタには virtual を付けなければいけない」のようなコーディング規約になってしまう(あるいは,そう盲目的に信じてしまう)のは良くないだろうと言う感想です.

コピーコンストラクタの扱い

この記事は,C++プログラマであるかを見分ける10の質問 - Life like a clown の「コピーコンストラクタおよび代入演算子の扱いにおける戦略について述べよ」に対する回答的な記事です.

C++ では,ユーザが明示的にコピーコンストラクタ,および代入演算子を定義しなかった場合,デフォルトのコピーコンストラクタ,および代入演算子が定義されます.この挙動は,各メンバ変数の値を単にコピーすると言うものなのでポインタなどが絡むと(ポインタ値がコピーされた後でコピー元のクラスで delete が発生するなどの)問題になる場合があります.そのため,コピーコンストラクタについては,

  1. 適切な動作を行うコピーコンストラクタ(および代入演算子)を明示的に定義する.
  2. 適切なコピーコンストラクタを定義できない場合は,コピー不可能にする.

と言う戦略を取ることになります.コピー禁止なクラスを作成する方法は,以下のように private にコピーコンストラクタ,および代入演算子の宣言だけを書いておきます.これによって,どこかでコピーが発生するようなコードが書かれた場合には,コンパイルエラーになってそれを防ぐ事ができます.

class Foo {
public:
    // 必要なメンバ関数などを定義する.
    // ...
private:
    // non-copyable
    Foo(const Foo&);
    Foo& operator=(const Foo&);
};

これをいちいち書くのは面倒と言う事で,Non-copyable クラス などを定義しておき,それを継承して使用すると言う事もしばしば行われます.

I/O のような何らかの資源(にアクセスするためのもの)をメンバ変数に持っている場合など,コピーコンストラクタの挙動をきちんと定義するのが難しいと言うケースはそれなりに存在するため,「コピー禁止にする」と言う選択肢を取る場面にも直々遭遇するのではないかなと思います.

ポインタの使用

この記事は,C++プログラマであるかを見分ける10の質問 - Life like a clown の「ポインタの使用方法について,メモリーリーク問題等と絡めながら戦略を述べよ」に対する回答的な記事です.

ポインタの使用に関する戦略ですが,経験上は以下の 2点辺りになるかと思います.

  • C++ は意外とポインタ(と言うよりは動的なメモリ確保)をユーザが明示的に使わずとも何とかなるケースが多いです.STL コンテナ等すでに用意されてあるライブラリの使用も考慮に入れ,本当にポインタが必要な場面かどうかを熟考しましょう.
  • ポインタが必要になった場合,「自力で後処理をする」と言う思想は危険なのでスマートポインタを使用するようにしましょう.

あんまりメモリリークの話絡めてないですね.

スマートポインタに関しては ポインタは用法用量を守って正しくお使いください - kikairoyaの日記 で上手く纏められていますので詳細はそちらを参照下さい.スマートポインタに関しては,現状では shared_ptr を使うと言う選択肢が一番多いだろうと予想されますので,「どの shared_ptr を使うか」と言う事について少し検討してみます.

まず,Boost C++ Libraries が使える(使う事を許されている)環境では boost::shared_ptr を使うのがベストでしょう.何らかの理由で Boost の使用が許されていない環境の場合は,コンパイラが比較的新しければ std::tr1::shared_ptr が存在するかと思いますのでそれを使用します.std::tr1::shared_ptr も存在しない場合は・・・自作になるでしょうか.ただし,shared_ptr および shared_array の実装は(その必要性から)多くの人が実装していますので,自分で作ると言う道を選ぶよりは既に誰かが実装した shared_ptr を持ってくる方が良いと思います.

現在の C++ に標準ライブラリとして唯一存在するスマートポインタである auto_ptr は扱いが難しいです.個人的な経験では,狭いスコープ内で new して一時変数に格納する(すぐに開放する)ようなケースでは auto_ptr でも良いかなと言う感じです.

纏めると以下のような形になります.

  1. Boost C++ Libraries が使えるなら boost::shared_ptr を使用する.
  2. TR1 が実装されている環境であれば std::tr1::shared_ptr を使用する.
  3. サードパーティ製の shared_ptr.h などを探して利用する.
  4. std::auto_ptr で代替する(限定的).

C++ でポインタが使われるケースは「コピーで発生するコストを嫌って」と言う話もしばしば耳にしますが,C++0x でムーブが定着するにつれてこの問題も少しずつ解消していく事が予想されます.その意味でも,そのコスト回避はどうしても必要なのかどうかはよく検討し,局所的な最適化はできるだけ抑えて,メモリ管理は標準ライブラリなどに任せるスタイルに移行しておく方が良いかなと個人的には思っています.

多重継承の概要

これは,C++プログラマであるかを見分ける10の質問 - Life like a clown の 「多重継承について概要を説明せよ」と言う質問に対する回答的な記事です.多重継承とは,その名の通り,以下のように 2 つ以上のクラスを継承する事を指します.

class Base1 {
public:
    virtual ~Base1() {}
    virtual void do_something1();
};

class Base2 {
public:
    virtual ~Base2() {}
    virtual void do_something2();
};

class Derived : public Base1, public Base2 {
    ...
};

一般的に多重継承になりやすいケースは,インターフェースや Mix-in と言った使い方をする場合でしょうか.Mix-in で一番よく使いそうな例だと Non-copyable Mixin 辺りかなと思います.「何らかのクラスを継承したクラスを作成したいんだけど non-copyable にしたい」と言ったケースでは多重継承はよく目にします.

多重継承で気をつけなければいけないのはダイヤモンド継承と呼ばれる問題です.あるクラスが継承した 2つ(以上)のクラスが,ある一つの基底クラスを継承していた場合,同じメンバ変数やメンバ関数(メソッド)が複数存在する事になるので,どの基底クラスのメンバー変数/関数を使うか曖昧さが残る事になります.これを仮想継承と言い,以下のように virtual キーワードを付与します.

class Base {
public:
    virtual ~Base() {}
    virtual void do_something();
    int foo;
};

class Derived1 : public virtual Base { // 仮想継承
public:
    virtual ~Derived1() {}
};

class Derived2 : public virtual Base { // 仮想継承
public:
    virtual ~Derived2() {}
};

class NestedDerived : public Derived1, public Derived2 {
    ...
};

多重継承は複雑になりがち,と言う理由で嫌う人もいるようです.多重継承を回避する方法としては,例えばインターフェースのような使い方だと,ポリシー・ベース(テンプレート・パラメータでインターフェース的なクラスを指定するようにする)でもある程度は代替できそうです.「多重継承だから禁止」のような方針はあまり良いとは思いませんが,まぁ各人が良いと思う形でクラス設計できれば良いのではないかと思います.

const 概要

これは,C++プログラマであるかを見分ける10の質問 - Life like a clown の「const の機能について概要を説明せよ」と言う質問に対する回答的な記事です.こんな時ですが,できるだけ平常更新を続けます.

C++ の const には大きく分けて 2つの役割があります.まず,基本的には const で宣言する事によって値が変更できなくなる(値を変更しようとするとコンパイルエラー)と言う役割があります.

const ing a = 100; // この値は変更不可

この機能を利用して,「ちょっとした一時変数であっても const にできる変数はできるだけ const 宣言し,想定外のエラーを減らそう(コンパイル時エラーで気付けるようにしよう)」と言うコーディングスタイルも提唱されていたりします.

さて,C++ のコードにおいて const をよく見かける機会は,「関数の引数」,「メンバ関数(メソッド)の宣言」の 2通りになります.

パラメータ

もっとも一般的なconstの使用方法としては、ポインタで指定されているオブジェクトや参照されているオブジェクトのデータの保護になります:

void func ( const MyObject * data ); // MyObjectはfuncの中では変更できません
void func ( const MyObject & data ); // MyObjectはfuncの中では変更できません

・・・(中略)・・・

メソッド

オブジェクトの内部のデータを変更しないインスタンスメソッドというのもよく作成されます。

・・・(中略)・・・

void MyClass::func ( MyOjbect & data ) const; // この関数はクラスのデータを変更することはできません

もしもconstのオブジェクトがあったとしたら、そのオブジェクトの中のメソッドのうち、呼び出せるのはconstの付いたメソッドだけです。理由としては、通常のメソッドが呼べてしまうとすると、そのオブジェクトのポインタをthisポインタとして渡す必要があります。しかし、そのクラスがconstが付いた”const MyClass”オブジェクトだったとすると、”MyClass *”ではなくて、”const MyClass *”しか得ることができませんので、constメソッドしか呼べないのです。

void MyClass::const_func() const;
void MyClass::func();
 
const MyClass object;
 
object.const_func(); // ok
object.func(); // constオブジェクトの非constクラスは呼ぶことができません

同様の理由で、constメソッドの中からそのオブジェクトのメソッドを呼ぶ場合には、他のconstメソッドしか呼ぶことはできません。

C + +のキーワード:const - cppreference.com

関数やクラスの設計で const 周りがおざなりの場合,const 付で渡されたが const メンバ関数が一つも定義されていないので const_cast を使うしかない,と言う状況がしばしば発生するので,この辺りの設計は注意して行う必要があります.

overload と override と hiding

これは, C++プログラマであるかを見分ける10の質問 - Life like a clown の 「overload と override と hiding の違いについて説明せよ」と言う質問に対する回答的な記事です.これらの差については,本の虫: C++におけるoverloadとoverrideとhiding 辺りでうまく纏まっているので引用して終わります.

同じ名前で、他のシグネチャの違う関数セットのことを、関数のオーバーロード(overload)という。

void f(int);
void f(double);

Derived classがBase classと同じvirtual関数を宣言しているとき、Derived classのvirtual関数は、Base classの同virtual関数を、オーバーライド(override)しているという。

struct Base
{
    virtual void f() {}
};

struct Derived : Base
{
    virtual void f() {} // override
};

Derived classのメンバー名に対し、同名のBase classのメンバーがある場合、Derived classのメンバーは、Base classのメンバーを隠している(hiding)という。

struct Base
{
    void f(int);
    int value;
};

struct Derived : Base
{
    void f(double);
    double value;
};

int main()
{
    Derived d;

    d.f(0); // void Derived::f(dobule)を呼ぶ
    d.value; // double Derived::valueである
}

結局これは、Derived classスコープのメンバーである名前が、Base classスコープのメンバーの名前を隠しているに過ぎない。同じことは、クラス以外のスコープでも起こる。

本の虫: C++におけるoverloadとoverrideとhiding

overload と override の違いは様々な所でよく言われるので分かると思いますが,C++ では*1これらの他に hiding と言うものが存在します.C++ において,メンバ関数を override するためには基底クラスの該当メンバ関数が virtual 宣言されていなくてはなりませんが,virtual 宣言されていなくても一見すると override されているような挙動を示します.これが hiding になります.

override と hiding の違いは,基底クラスのポインタで操作を行うような場合に問題になってきます.virtual 宣言のなされたメンバ関数は基底クラスのポインタから該当のメンバ関数を呼び出しても継承先クラスの override したメンバ関数の挙動を示しますが,virtual 宣言されていないメンバ関数の場合は基底クラスの挙動を示します.このような違いがあるので,問題となっているクラスがインターフェース的な使われた方をする場合は特に,注意して使用する必要があります.

*1:別に C++ 以外のプログラミング言語でもありそうですが.