web-dev-qa-db-ja.com

C ++ヘッダーファイル、コード分離

私はC++を初めて使用し、コードの分離についていくつかの一般的な質問がありました。私は現在、小さなアプリケーションをすべて1つのファイルで作成しています。私が今やりたいのは、これを別々のファイルに変換して、同様のコードなどが含まれるようにすることです。今の私の本当の質問は、物事を分離する方法をどうやって知るのかということです。コードを分離する必要がある目に見えないマージンはどれくらいですか?

また、ヘッダーファイルのポイントは何ですか?コンパイル中にリンカーに含まれる前にコードで使用できるように、メソッドとクラスを前方宣言するのですか?

方法やベストプラクティスについての洞察は素晴らしいでしょう、ありがとう!

29
Parkman

ヘッダーファイルには、クラスと関数の宣言が含まれている必要があります。

ソースファイルには、クラスと関数の定義が含まれています。

ヘッダーファイルごとに1つの宣言を持ち、ソースファイルごとに1つの定義を持つのが標準的な方法です(つまり、読みやすくなります)。ただし、小さい(より単純なヘルパーを読む)オブジェクトの場合は、関連するより実質的なオブジェクトとグループ化することがあります。

例:クラスメニュー

Menu.h:     Contains the Menu declaration.
Menu.cpp:   Contains the Menu definition.

ヘッダーファイルに宣言が含まれている理由は、複数のソースファイルから宣言を含めることができるためです。したがって、各ソースファイルには、各クラスと関数の定義がまったく同じです。

このように考えてください:
ヘッダーファイルがない場合は、すべてのソースファイルにクラスおよび/または関数の宣言(定義なし)を含める必要があります。これは、すべてのファイルに同じ宣言のコピーがあることを意味します。したがって、クラスを変更する場合は、すべてのファイルで同じ変更を行う必要があります。ヘッダーファイルを使用すると、宣言が1か所にあるため、変更するオブジェクトは1つだけになります。

25
Martin York

まず、必要なファイル以外のファイルで表示する必要のないものをヘッダーに配置しないでください。次に、必要なものを以下に定義しましょう。

翻訳ユニット

翻訳ユニットは、コンパイルされている現在のコードであり、直接または間接的に、それに含まれるすべてのコードです。 1つの変換ユニットが1つの.o/.objファイルに変換されます。

プログラム

これが、プロセスを形成するために実行できる1つのバイナリファイルにリンクされたすべての.o /.objファイルです。

異なる翻訳単位を持つことの主なポイントは何ですか?

  1. 依存関係を減らして、1つのクラスの1つのメソッドを変更した場合に、プログラムのすべてのコードを再コンパイルする必要はなく、影響を受ける変換ユニットのみを再コンパイルする必要があります。アン
  2. 他の翻訳ユニットがリンクしているときに表示されない翻訳ユニットのローカル名を使用することで、名前の衝突の可能性を減らします。

では、コードをさまざまな翻訳単位に分割するにはどうすればよいでしょうか。答えは「そうする!」というものはありませんが、ケースバイケースで検討する必要があります。さまざまなクラスがあり、さまざまな翻訳単位に配置できるし、配置する必要があるため、多くの場合明らかです。

foo.hpp:

/* Only declaration of class foo we define below. Note that a declaration
 * is not a definition. But a definition is always also a declaration */
class foo;

/* definition of a class foo. the same class definition can appear 
   in multiple translation units provided that each definition is the same  
   basicially, but only once per translation unit. This too is called the  
   "One Definition Rule" (ODR). */
class foo {
    /* declaration of a member function doit */
    void doit();

    /* definition of an data-member age */
    int age;
};

いくつかの無料の関数とオブジェクトを宣言します。

/* if you have translation unit non-local (with so-called extern linkage)  
   names, you declare them here, so other translation units can include  
   your file "foo.hpp" and use them. */
void getTheAnswer();

/* to avoid that the following is a definition of a object, you put "extern"  
   in front of it. */
extern int answerCheat;

foo.cpp:

/* include the header of it */
#include "foo.hpp"

/* definition of the member function doit */
void foo::doit() {
    /* ... */
}

/* definition of a translation unit local name. preferred way in c++. */
namespace {
    void help() {
        /* ... */
    }
}

void getTheAnswer() {
    /* let's call our helper function */
    help();
    /* ... */
}

/* define answerCheat. non-const objects are translation unit nonlocal  
   by default */
int answerCheat = 42;

bar.hpp:

/* so, this is the same as above, just with other classes/files... */
class bar {
public:
    bar(); /* constructor */
}; 

bar.cpp:

/* we need the foo.hpp file, which declares getTheAnswer() */
#include "foo.hpp"
#include "bar.hpp"

bar::bar() {
    /* make use of getTheAnswer() */
    getTheAnswer();
}

(上記のように)匿名の名前空間内の名前は、翻訳単位がローカルであるように見えるため、衝突しないことに注意してください。実際にはそうではなく、衝突しないように一意の名前を持っているだけです。翻訳ユニットのローカル名が本当に必要な場合(たとえば、Cコードが関数を呼び出すことができるようにcとの互換性があるため)、次のように実行できます。

static void help() { 
    /* .... */
}

ODRは、1つのプログラムにオブジェクトまたは非インライン関数の定義を複数持つことはできないとも述べています(クラスはオブジェクトではなく型であるため、それらには適用されません)。したがって、非インライン関数をヘッダーに配置したり、「intfoo;」のようなオブジェクトを配置したりしないように注意する必要があります。ヘッダーで。これにより、リンカーがそれらのヘッダーを含む変換ユニットをリンクしようとしたときに、リンカーエラーが発生します。

私はあなたを少し助けることができると思います。長い答えでしたが、確かにどこかにエラーがあります。翻訳単位が厳密に別の方法(プリプロセッサの出力)で定義されていることを私は知っています。しかし、それを上記に含めることは大きな価値をもたらさないと私は思います、そしてそれは問題を混乱させるでしょう。本当のバグを見つけたら、遠慮なく私を叩いてください:)

コードをさまざまなクラス/関数に分割する方法を決定することは、プログラミングの主要なタスクの1つです。これを行う方法にはさまざまなガイドラインがあります。開始するには、C++とオブジェクト指向設計に関するチュートリアルを読むことをお勧めします。

いくつかの基本的なガイドラインは

  • 一緒に使われるものをまとめる
  • ドメインオブジェクト(ファイル、コレクションなど)のクラスを作成します

ヘッダーファイルを使用すると、クラスまたは関数を宣言して、それをいくつかの異なるソースファイルで使用できます。たとえば、ヘッダーファイルでクラスを宣言する場合

// A.h
class A
{
public:
    int fn();
};

その後、このクラスをいくつかのソースファイルで使用できます。

// A.cpp
#include "A.h"
int A::fn() {/* implementation of fn */}

//B.cpp
#include "A.h"
void OtherFunction() {
    A a;
    a.fn();
}

したがって、ヘッダーファイルを使用すると、宣言を実装から分離できます。すべて(宣言と実装)をソースファイル(例:A.cpp)に入れる場合は、それを2番目のファイル(例:

// B.cpp
#include  "A.cpp" //DON'T do this!

次に、B.cppをコンパイルできますが、プログラムをリンクしようとすると、リンカーは、複数定義されたオブジェクトがあると文句を言います。これは、Aの実装のコピーが複数あるためです。

4
David Dibben

提案:1。今すぐアプリケーションのデザインを準備してください。 2.設計に基づいて、相互作用する必要なオブジェクトを作成します。 3.新しく作成したデザインに合わせて、既存のコードをリファクタリングまたは完全に変更します。

ヘッダーファイルは、その機能を使用する可能性のある他のクラスへのインターフェイスを提供します。

0
Elroy