web-dev-qa-db-ja.com

C ++でクラスごとに1つのファイルを効果的に使用するにはどうすればよいですか?

Javaでのオブジェクト指向プログラミングの適切な経験と、Cの基本的な知識があるにもかかわらず、私は他の人に期待していたC++の精神的なブロックに少し遭遇しました。開発者は私が光を当てるのを助けることができます。

Javaでは、クラスを定義するたびに、新しい.Javaファイルを自然に作成します。もちろん、内部クラスと匿名クラスの例外はありますが、概して、新しいクラスは新しいファイルを意味します。新しいクラスには、それらを整理するためのパッケージなどがあります。C++で手を出し始めたので、クラスごとに1つの.Javaファイルという同じアプローチを採用し、クラスごとに1つの.hppファイルと1つの.cppファイルに拡張しました。 。ただし、このアプローチには多くの問題があるようです。最近気になっているのはこんな感じです。

ゲームの世界を表すクラスとユニットを表すクラスがあるようなゲームがあるとします。ゲームのワールドクラスにユニットを特定の場所に配置するメソッドがあれば、それで十分です。ただし、ユニットから包含ワールドを取得する方法が必要な場合、各ヘッダーに他のヘッダーが含まれているため、プロジェクトの構築に使用されている方法でincludeディレクティブが解決されません。もちろん、クリーンなソリューションが必要であることはわかっています。経験豊富な開発者が、C++プロジェクトの編成に関するリソースや、先ほど述べた特定の構成要素の処理についてアイデアを持っているかどうかを知りたいです。

6
SaxSalute

C++では、ファイルを整理する方法に完全な柔軟性があります。しかし、良い選択をするには、この自由に慣れる必要があります。

  • 最初のプラクティスは、ヘッダーに include guards を含めることです。これにより、依存関係が共有されるため、同じヘッダーが複数回含まれることを回避できます。

  • もう1つは規律です。ヘッダーを作成します self-sufficient にし、常に必要なすべてのヘッダー依存関係をヘッダーに含めます。インクルードするコードが他にインクルードする必要があるものについて 仮定なし を作成します。相互依存関係を心配する必要はありません。これは通常 前方宣言宣言と実装の分離 によって処理されます。

  • 3つ目は、ヘッダーにどの程度の粒度を設定するかを一貫して決定して使用することです。クラスごとにヘッダーが必要ですか(慣れている場合は、続行しないでください)?または、より高い論理ユニット(例:クラスとその補助)のヘッダーを好みますか?

  • 次に、 ディレクトリと名前空間 の質問に来ます(おそらく、Java "パッケージ"という概念に近いもの):異なる名前空間のファイルを保持していますか?別のディレクトリにあるかどうか?このリフレクションは、ライブラリサブプロジェクトを作成するかどうかという問題にも緩やかに関連している可能性があることに注意してください。

  • 開発環境も選択したディレクトリ構造に影響を与える可能性があることを忘れないでください( example または example )。

将来的には、 C++モジュール の概念に注目するのは興味深いでしょう。これはまだ標準ではないため、現在のアプローチをここで構築するべきではありません。 Javaパッケージをファッションのように使用してコードのコンポーネント化を容易にすることを意図しているため、それは徐々にヘッダーを取り除きますが、それでもファイル編成に大きな自由を与えることができます。 。

7
Christophe

循環依存関係を解決するためのオプションは次のとおりです。

  1. 前方宣言

    はい、これは実際に一般的に使用されており、ハッキングとは見なされません。

    相互に依存する2つの型のパブリックインターフェイスのみが前方宣言を必要とすることに注意してください。implementationが行外の場合、それは別個の翻訳単位であり、他のヘッダーを含めてそれぞれに問題はありません。

    また、Basileが示唆するようにすべてを1つのヘッダーに含めたとしても、実際の宣言が同じヘッダーで後に続く場合でも、前方宣言を使用することはまったく正常です。

  2. 型消去

    ユニットに、実際のゲームクラスの抽象基本クラスへのポインターまたは参照を与えます。これ自体はユニットタイプに依存しません。ただし、これは呼び出し側にキャストを強制します。

    void* ABCを導入する代わりに、それはさらに醜いです。

ただし、可能であれば、依存関係を完全に解除することをお勧めします。

ゲームコンテキストではなく実際にユニットオブジェクトがあるのはいつですか?すべてのユニットオブジェクトを大きくして、呼び出し元がすでに持っている状態への参照を含めるのではなく、単にそれを渡すことはできませんか?

6
Useless

ゲームの世界を表すクラスとユニットを表すクラスがあるようなゲームがあるとします。ゲームのワールドクラスにユニットを特定の場所に配置するメソッドがあれば、それで十分です。

あなたはこのようなものを意味していると思います

// Gamworld.hpp
#ifndef GAMEWORLD_HPP
#define GAMEWORLD_HPP
#endif

#include "Unit.hpp"

class Gameworld
{
     public:
       Unit GetUnit();
};

ただし、ユニットから包含ワールドを取得する方法が必要な場合、各ヘッダーに他のヘッダーが含まれているため、プロジェクトの構築に使用されている方法でincludeディレクティブを解決できません。

いいえ、前方宣言を使用し、そこでポインター、参照、または共有ポインターのみを使用する場合は、Unit.hppにGameworld.hppを含める必要はありません。

// Unit.hpp
#ifndef UNIT_HPP
#define UNIT_HPP
#endif

class Gameworld;

class Unit
{  
      Gameworld *world;

   public:
       Gameworld *GetWorld();
}

ユニットにはGameworld.hppのみを含めます。cpp

// Unit.cpp
#include "Gameworld.hpp"

Gameworld *Unit::GetWorld()
{
    return world;
}

基本的な考え方を理解していただければ幸いです。少し時代遅れですが、Lakosの本 "Large Scale C++ Software Design" のコピーを入手することを強くお勧めします。これは、知っておく必要がある物理的なC++ファイルレイアウトに関するすべての残酷な詳細と戦略を説明します。

6
Doc Brown

C++ 11(およびC++の他の方言、およびC)では、特定のプログラムに対していくつかの 翻訳単位 を使用できます。したがって、プログラムは_foo.cc_、_bar.cc_、_gee.cc_ C++ソースファイル コンパイル済み を個別に、および リンクされた を組み合わせて作成できます。 [〜#〜] gcc [〜#〜] を使用している場合(例:Linux)、プログラムをコンパイルします。例:.

_ g++ -Wall -g -c foo.cc
 g++ -Wall -g -c bar.cc
 g++ -Wall -g -c gee.cc
_

上:_-Wall_はすべての警告を要求し、_-g_はデバッグ情報を要求し、_-c_はリンクなしのコンパイルのみを要求します。

(つまり、_foo.cc_は_foo.o_ オブジェクトファイル にコンパイルされ、_bar.cc_は_bar.o_にコンパイルされます)、後でそれを(progbinに)リンクします 実行可能

_ g++ -g foo.o bar.o gee.o -o progbin
_

実際には、毎回コマンドを入力することはありませんが、 make のようなビルダーを使用して上記のコマンドを駆動および実行します( this および thatを参照)Makefile- s )の例上記のコマンドは単純な場合のものです。詳細については GCCの呼び出し に関するドキュメントを参照してください(および_g++_への引数の順序は重要です!)。インクルードディレクトリをコンパイルするための_-I_dirオプションを追加することがよくありますdirおよび_-L_dir&_-l_nameリンクのオプション(ライブラリディレクトリdirを追加し、指定されたライブラリを使用name)。

同じ宣言を各ソースファイルに複数回記述(およびコピー/貼り付け)することを避けたい場合。 フォワード宣言不透明なポインタ型 を使用することができます(必要になる場合もあります) スマートポインタ を広範囲に使用したい)。 PIMPLイディオム 。したがって、すべてのソースファイルで _myprog.h_- d になる共通の_#include_ヘッダーファイル(またはいくつかのヘッダーファイル)があります_foo.cc_、_bar.cc_など...)。

ヘッダーファイルとソースファイルに関する義務はありません。特に(Javaとは対照的に)several宣言をヘッダーファイルに、severalクラスと関数定義(&実装)をソースに含めることができますファイル。

したがって、小規模な(50KLOC未満の)プロジェクトの場合、 precompiled の単一の共通ヘッダーファイル(およびすべての宣言)といくつかのソースファイルを用意することをお勧めします。しかし、それは主に意見の問題です:私は単一のヘッダーファイル(数千行)といくつかのソースファイル(数千行)が好きですが、many小さいヘッダーファイルが好きな人もいます小さなソースファイル(コンパイル時間が遅くなります)。

プログラムをデバッグしたら(gdbおよびvalgrindを使用して、おそらく instrumentation options を使用して、_-fsanitize=address_または_-fsanitize=undefined_または他の_-fsanitize=_をコンパイルしてリンクします。オプション) profile またはベンチマークすることができます。プロファイリングの場合、_-pg_の後に_-g_を付けてコンパイルし、 gprof またはoprofileを使用します。ベンチマークについては、_-O2_で最適化するようコンパイラーに要求する必要があります。

実際には、具体的な例については、既存の フリーソフトウェア コード(例: github )を調べてください。