1. ホーム
  2. c++

[解決済み] C++のヘッダーファイルはどのように実装を含めることができますか?

2023-01-12 06:42:29

質問

C/C++の専門家ではありませんが、ヘッダーファイルのポイントは関数を宣言することであり、C/CPPファイルは実装を定義することだと思っていました。

しかしながら、今夜いくつかの C++ コードを見直したところ、クラスのヘッダーファイルでこれを見つけました...。

public:
    UInt32 GetNumberChannels() const { return _numberChannels; } // <-- Huh??

private:
    UInt32 _numberChannels;

では、なぜヘッダーに実装があるのでしょうか?それは const キーワードと関係があるのでしょうか?クラスメソッドをインライン化するのでしょうか?この方法で行うことの利点/ポイントは、CPP ファイルで実装を定義することと比較して、正確には何ですか?

どのように解決するのですか?

OK、決してC/C++の専門家ではありませんが、ヘッダーファイルのポイントは関数を宣言することであり、次にC/CPPファイルは実装を定義することだと考えていました。

ヘッダーファイルの真の目的は、複数のソースファイル間でコードを共有することです。 それは 一般的に 宣言と実装を分離してコード管理をしやすくするために使用されますが、これは必須ではありません。 ヘッダファイルに依存しないコードを書くことは可能ですし、ヘッダファイルだけで構成されたコードを書くことも可能です(STLやBoostライブラリはその良い例です)。 覚えておいてほしいのは プリプロセッサー に遭遇したとき #include 文に出会うと、その文を参照されているファイルの内容に置き換えます。 コンパイラ は完成した前処理されたコードだけを見ることになります。

ですから、例えば次のようなファイルがあったとします。

Foo.h:

#ifndef FooH
#define FooH

class Foo
{
public:
    UInt32 GetNumberChannels() const;

private:
    UInt32 _numberChannels;
};

#endif

Foo.cppです。

#include "Foo.h"

UInt32 Foo::GetNumberChannels() const
{
    return _numberChannels;
}

Bar.cppです。

#include "Foo.h"

Foo f;
UInt32 chans = f.GetNumberChannels();

この プリプロセッサー は Foo.cpp と Bar.cpp を別々に解析し、以下のコードを生成します。 コンパイラ がパースします。

Foo.cppを解析します。

class Foo
{
public:
    UInt32 GetNumberChannels() const;

private:
    UInt32 _numberChannels;
};

UInt32 Foo::GetNumberChannels() const
{
    return _numberChannels;
}

Bar.cppです。

class Foo
{
public:
    UInt32 GetNumberChannels() const;

private:
    UInt32 _numberChannels;
};

Foo f;
UInt32 chans = f.GetNumberChannels();

Bar.cppはBar.objにコンパイルされ、以下を呼び出すための参照を含んでいます。 Foo::GetNumberChannels() . Foo.cppはFoo.objにコンパイルされ、以下の実装が含まれます。 Foo::GetNumberChannels() . コンパイル後 リンカ は .obj ファイルをマッチングし、最終的な実行ファイルを生成するためにそれらをリンクします。

では、なぜヘッダーに実装があるのでしょうか?

メソッド宣言の中にメソッドの実装を含めることで、暗黙のうちにインライン化を宣言していることになります(実際には inline キーワードを明示的に使用することもできます)。 コンパイラが関数をインライン化するよう指示することは、その関数が実際にインライン化されることを保証しないヒントに過ぎません。しかし、インライン化された場合、インライン化された関数がどこから呼び出されても、関数の内容は CALL ステートメントを生成して関数に飛び込み、終了時に呼び出し元にジャンプバックするのではなく、関数の内容が呼び出し元に直接コピーされます。 これにより、コンパイラーは周囲のコードを考慮し、可能であればコピーされたコードをさらに最適化することができます。 

constキーワードと関係があるのでしょうか?

いいえ。 const キーワードは単に、そのメソッドが実行時に呼び出されるオブジェクトの状態を変更しないことをコンパイラに示すだけです。

CPPファイルで実装を定義するのに対して、この方法で行う利点/ポイントは正確に何ですか?

効果的に使用すると、通常、コンパイラーはより速く、より最適化されたマシン・コードを生成することができます。