web-dev-qa-db-ja.com

.hファイル内または.cppファイル内のクラスの実装の違い

ヘッダーでクラスのプロトタイプを作成し、効果的な.cppファイルで実装する通常のアプローチと比較して、ヘッダーファイルでのみクラスを宣言して実装する場合の違いはどれかと思いました。

私が話していることをよりよく説明するために、私は通常のアプローチ間の違いを意味します:

// File class.h
class MyClass 
{
private:
  //attributes
  public:
  void method1(...);
  void method2(...);
  ...
};

//file class.cpp
#include "class.h"

void MyClass::method1(...) 
{
  //implementation
}

void MyClass::method2(...) 
{
  //implementation
}

そしてjust-headerアプローチ:

// File class.h
class MyClass 
{
private:
  //attributes
public:
  void method1(...) 
  {
      //implementation
  }

  void method2(...) 
  {
    //implementation
  }

  ...
};

私は主な違いを得ることができます:2番目のケースでは、コードは同じ実装のより多くのインスタンスを生成する必要がある他のすべてのファイルに含まれているため、暗黙的な冗長性があります。最初のケースでは、コードはそれ自体でコンパイルされ、MyClassのオブジェクトを参照するすべての呼び出しはclass.cppの実装にリンクされます。

しかし、他の違いはありますか?状況に応じて、別の方法を使用する方が便利ですか?また、メソッドの本体をヘッダーファイルに直接定義することは、そのメソッドをインライン化するためのコンパイラーへの暗黙的な要求であるとどこかで読んだことがありますが、それは本当ですか?

31
Jack

主な実用的な違いは、メンバー関数の定義がヘッダーの本文にある場合、もちろん、それらはそのヘッダーを含む各翻訳単位に対して1回コンパイルされるということです。プロジェクトに数百または数千のソースファイルが含まれていて、問題のクラスがかなり広く使用されている場合、これは多くの繰り返しを意味する可能性があります。各クラスが2つまたは3つだけで使用されている場合でも、ヘッダーのコードが多いほど、実行する作業が多くなります。

メンバー関数定義が独自の変換単位(.cppファイル)にある場合、それらは1回コンパイルされ、関数宣言のみが複数回コンパイルされます。

クラス定義で定義されている(宣言されていない)メンバー関数が暗黙的にinlineであることは事実です。ただし、inlineは、人々がそれを合理的に推測するとは限らないという意味ではありません。 inlineは、関数の複数の定義が異なる翻訳単位で表示され、後で一緒にリンクされることは正当であると述べています。これは、異なるソースファイルが使用するヘッダーファイル内にクラスが存在する場合に必要です。そのため、この言語は役に立ちます。

inlineは、関数をインライン化すると便利であるというコンパイラへのヒントでもありますが、名前にかかわらず、これはオプションです。コンパイラが高度になるほど、インライン化に関する独自の決定を行うことができ、ヒントの必要性が少なくなります。実際のインラインタグよりも重要なのは、関数がコンパイラーで使用できるかどうかです。関数が別の変換単位で定義されている場合は、その呼び出しがコンパイルされるときに使用できません。したがって、何かが呼び出しをインライン化する場合は、コンパイラーではなくリンカーでなければなりません。

それを行うための3番目の可能な方法を検討することで、違いをよりよく見ることができる場合があります。

// File class.h
class MyClass
{
    private:
        //attributes
    public:
       void method1(...);
       void method2(...);
       ...
};

inline void MyClass::method1(...)
{
     //implementation
}

inline void MyClass::method2(...)
{
     //implementation
}

暗黙のインラインが邪魔にならないようになりましたが、この「すべてのヘッダー」アプローチと「ヘッダーとソース」アプローチの間にはいくつかの違いが残っています。コードを翻訳単位間でどのように分割するかによって、コードの作成時に何が起こるかが決まります。

36
Steve Jessop

実装を含むヘッダーを変更すると、そのヘッダーを含む他のすべてのクラスが強制的に再コンパイルおよび再リンクされます。

ヘッダーは実装よりも頻繁に変更されないため、実装を別のファイルに配置することで、コンパイル時間を大幅に節約できます。

他のいくつかの回答ですでに指摘されているように、はい、ファイルのclassブロック内でメソッドを定義すると、コンパイラーがインライン化されます。

7
Ben S

はい、コンパイラは次のようにヘッダーファイルで直接宣言されたメソッドをインライン化しようとします。

class A
{
 public:
   void method()
   {
   }
};

ヘッダーファイルの実装を分離する際に、次のような便利な方法を考えることができます。

  1. 同じコードが複数の翻訳単位に含まれるため、コードが膨張することはありません
  2. コンパイル時間が大幅に短縮されます。ヘッダーファイルの変更については、コンパイラーはそれを直接または間接的に含む他のすべてのファイルをビルドする必要があることに注意してください。ヘッダーファイルにスペースを追加するためだけに、バイナリ全体を再度ビルドするのは非常にイライラすると思います。
7
Naveen

はい、クラス定義内でメソッドを定義することは、inlineを宣言することと同じです。他に違いはありません。ヘッダーファイルですべてを定義してもメリットはありません。

テンプレートメンバーの定義もヘッダーファイルに含める必要があるため(通常、ほとんどのコンパイラーがexportをサポートしていないため)、テンプレートクラスを使用するC++では通常、このようなことが発生します。ただし、通常の非テンプレートクラスでは、メソッドをinlineとして宣言する必要がない限り、これを行う意味はありません。

2
AnT

私にとっての主な違いは、ヘッダーファイルがクラスの「インターフェイス」のようなものであり、クライアントがそれらの特定の実装について心配することなく、そのパブリックメソッド(サポートする操作)をクライアントに伝えることです。つまり、cppファイルのみが変更されるため、コンパイル時間が大幅に短縮されるため、実装の変更からクライアントをカプセル化する方法です。

1
lalitm

以前は、さまざまなCORBAディストリビューションの違いからシールドするモジュールを作成し、さまざまなOS /コンパイラ/ CORBA libの組み合わせで均一に機能することが期待されていました。ヘッダーファイルに実装することで、単純なインクルードでプロジェクトに追加するのがより簡単になりました。同じ手法により、コードを呼び出すコードが別のライブラリまたは別のOSでコンパイルされているときに再コンパイルが必要なときに、コードが同時に再コンパイルされることが保証されました。

だから私のポイントは、さまざまなプロジェクトで再利用および再コンパイルできると予想されるかなり小さなライブラリがある場合、ヘッダーを作成すると、メインプロジェクトに追加のファイルを追加したり、外部ライブラリを再コンパイルしたりするのではなく、他のいくつかのプロジェクトとの統合に利点があるということです/ objファイル。

0
jszpilewski