web-dev-qa-db-ja.com

ヘッダー(繰り返し関数宣言)なしでC ++コードを書くことはできますか?

C++でプログラミングする場合、関数宣言を2回(ヘッダー)記述しなくても、コンパイルの同じスケーラビリティ、デバッグの明快さ、および設計の柔軟性を維持する方法はありますか?

56
thewreck

Lzz を使用します。単一のファイルを取り、すべての宣言/定義を適切な場所に自動的に作成して.hおよび.cppを作成します。

Lzzは非常に強力で、テンプレート、特殊化などを含む完全なC++構文の99%を処理します。

150120を更新:

新しいC++ '11/14構文は、Lzz関数本体内でのみ使用できます。

57
Richard Corden

Cを書き始めたときも同じように感じたので、これも調べました。答えは、はい、それは可能であり、いいえ、あなたはしたくないということです。

まずはい。

GCCでは、これを行うことができます。

// foo.cph

void foo();

#if __INCLUDE_LEVEL__ == 0
void foo() {
   printf("Hello World!\n");
}
#endif

これには意図した効果があります。ヘッダーとソースの両方を1つのファイルに結合し、インクルードとリンクの両方を行うことができます。

次に、なしで:

これは、コンパイラがソース全体にアクセスできる場合にのみ機能します。配布したいラ​​イブラリを作成するときにこのトリックを使用することはできませんが、クローズドソースを保持します。完全な.cphファイルを配布するか、別の.hファイルを作成して、.libを使用する必要があります。ただし、マクロプリプロセッサを使用して自動生成することもできます。しかし、それは毛むくじゃらになるでしょう。

そして、これを望まない理由#2、そしておそらくそれが最良の理由です:コンパイル速度。通常、Cソースファイルは、ファイル自体が変更されたとき、または変更が含まれているファイルのいずれかが変更されたときにのみ再コンパイルする必要があります。

  • Cファイルは頻繁に変更されますが、変更には変更された1つのファイルの再コンパイルのみが含まれます。
  • ヘッダーファイルはインターフェイスを定義するため、頻繁に変更されることはありません。ただし、実行すると、それらを含むすべてのソースファイルの再コンパイルがトリガーされます。

すべてのファイルがヘッダーファイルとソースファイルを結合している場合、すべての変更がすべてのソースファイルの再コンパイルをトリガーします。 C++は今でもコンパイル時間が短いことで知られていません。プロジェクト全体を毎回再コンパイルしなければならない場合はどうなるか想像してみてください。次に、複雑な依存関係を持つ数百のソースファイルのプロジェクトに外挿します...

37
rix0rrr

申し訳ありませんが、C++でヘッダーを削除するための「ベストプラクティス」などはありません。これは悪い考えです。そんなに嫌いな人には、3つの選択肢があります。

  • C++の内部構造と使用しているコンパイラーをよく理解してください。平均的なC++開発者とは異なる問題に遭遇することになるので、おそらく多くの助けを借りずにそれらを解決する必要があります。
  • 落ち込むことなく「適切」に使用できる言語を選択する
  • それらを生成するツールを入手してください。まだヘッダーはありますが、入力の手間を省きます
27
ojrac

彼の記事 C++の契約による設計の簡単なサポート で、Pedro Guerreiroは次のように述べています。

通常、C++クラスには、ヘッダーファイルと定義ファイルの2つのファイルがあります。アサーションは仕様であるため、ヘッダーファイルのどこにアサーションを記述する必要がありますか?または、定義ファイル内で、実行可能だからですか?または、両方で、矛盾のリスクを実行します(および作業を複製します)?代わりに、従来のスタイルを放棄し、ヘッダーファイルのみを使用して、Java and Eiffel do 。

これは、C++の正規性からの抜本的な変更であり、最初から努力を殺す危険性があります。一方、クラスごとに2つのファイルを維持するのは非常に厄介であるため、遅かれ早かれC++開発環境が登場し、それを隠してしまい、クラスの保存場所を気にせずにクラスに集中できるようになります。

それは2001年でした。私は同意しました。 2009年になりましたが、「それを私たちから隠し、クラスに集中できる開発環境」はまだ登場していません。代わりに、長いコンパイル時間が標準です。


注:上記のリンクは現在無効になっているようです。これは、著者のWebサイトの Publications セクションに表示される、出版物への完全な参照です。

Pedro Guerreiro、C++での契約による設計の単純なサポート、TOOLS USA 2001、Proceedings、24〜34ページ、IEEE、2001年。

10
Daniel Daranas

ヘッダーを回避する実用的な方法はありません。できることは、すべてのコードを1つの大きなc ++ファイルに入れることだけです。それは維持できない混乱で終わるので、それをしないでください。

現時点では、C++ヘッダーファイルは必要不可欠です。私はそれらが好きではありませんが、それらを回避する方法はありません。しかし、この問題に関するいくつかの改善点と新鮮なアイデアを楽しみにしています。

ところで-一度慣れてしまえば、もう悪くありません。thatもう悪いことではありません。

8

あなたのような人が見たのは、 writeeverythingin the headers です。これにより、メソッドプロファイルを1回記述するだけで済むという望ましい特性が得られます。

個人的には、宣言と定義を分離する方が良い理由は非常にあると思いますが、これがあなたを苦しめているなら、あなたが望むことをする方法があります。

8
T.E.D.

ヘッダーファイル生成ソフトウェアがあります。使用したことはありませんが、検討する価値があるかもしれません。たとえば、mkhdr!おそらくCおよびC++ファイルをスキャンし、適切なヘッダーファイルを生成します。

(ただし、Richardが指摘しているように、これにより特定のC++機能の使用が制限されているようです。代わりにRichardの回答を参照してください このスレッドの右側 。)

5
0scar

実際には、関数宣言を2回(ヘッダーファイルに1回、実装ファイルに1回)記述する必要があります。関数の定義(実装とも呼ばれます)は、実装ファイルに一度書き込まれます。

すべてのコードをヘッダーファイルに書くことができます(実際にはC++の汎用プログラミングで非常によく使用されます)が、これはそのヘッダーを含むすべてのC/CPPファイルがそれらのヘッダーファイルから実装の再コンパイルを意味することを意味します。

C#やJavaに似たシステムを考えている場合、C++では不可能です。

5

Visual Studio 2012でVisual-Assist Xについて言及した人はまだいません。

ヘッダーを維持する苦労を軽減するために使用できるメニューとホットキーがたくさんあります。

  • 「宣言の作成」は、関数宣言を現在の関数から.hppファイルにコピーします。
  • 「リファクタリング..署名の変更」を使用すると、1つのコマンドで.cppファイルと.hファイルを同時に更新できます。
  • Alt-Oを使用すると、.cppファイルと.hファイルを瞬時に切り替えることができます。
3
Contango

実際...実装全体をファイルに書き込むことができます。テンプレートクラスは、ヘッダーファイルですべて定義され、cppファイルはありません。

必要な拡張子を付けて保存することもできます。次に、#includeステートメントで、ファイルをインクルードします。

/* mycode.cpp */
#pragma once
#include <iostreams.h>

class myclass {
public:
  myclass();

  dothing();
};

myclass::myclass() { }
myclass::dothing()
{
  // code
}

その後、別のファイルで

/* myothercode.cpp */
#pragma once
#include "mycode.cpp"

int main() {
   myclass A;
   A.dothing();
   return 0;
}

いくつかのビルドルールを設定する必要があるかもしれませんが、動作するはずです。

3
Kieveli

あなたの問題を理解しています。 C++の主な問題は、Cから継承したコンパイル/ビルドメソッドであると言えます。C/ C++ヘッダー構造は、コーディングがより少ない定義とより多くの実装を伴うときに設計されました。私にボトルを投げないでください、しかしそれはそれがどのように見えるかです。

それ以来、OOPは世界を征服し、世界は実装よりも定義に重点を置いています。その結果、ヘッダーを含めることは、プリコンパイルされたヘッダーを使用したこれらすべての魔法は、TDD、リファクタリングツール、一般的な開発環境に関してはあまり役に立ちません。

もちろん、Cプログラマーは、コンパイラーが重いヘッダーファイルを持っていないので、これにあまり悩まされていません。そのため、非常に単純で低レベルのコンパイルツールチェーンに満足しています。 C++では、これは苦しみの歴史です。無限の前方宣言、プリコンパイル済みヘッダー、外部パーサー、カスタムプリプロセッサーなど。

ただし、多くの人は、C++が高レベルおよび低レベルの問題に対する強力かつ最新のソリューションを備えた唯一の言語であることを認識していません。適切なリフレクションとビルドシステムを備えた他の言語を採用すべきだと言うのは簡単ですが、それで低レベルのプログラミングソリューションを犠牲にしなければならず、低レベルの言語が混在するものを複雑にする必要があるのは無意味です仮想マシン/ JITベースのソリューションを使用します。

Dに似た「ユニット」ベースのc ++ツールチェーンを持つことは、地球上で最もクールなことだと今しばらく考えています。この問題は、クロスプラットフォームパーツで発生します:オブジェクトファイルは情報を保存できますが、問題はありませんが、Windowsではオブジェクトファイルの構造がELFの構造と異なるため、クロスプラットフォームソリューションを実装して中途半端なデータを保存および処理するのは苦痛です-コンパイル単位。

2
progician

君は できる ヘッダーを避けます。完全に。しかし、私はそれをお勧めしません。

いくつかの非常に具体的な制限に直面します。そのうちの1つは、循環参照を使用できないことです(クラスParentにクラスChildNodeのインスタンスへのポインターを含めることはできず、クラスChildNodeにはクラスParentのインスタンスへのポインターを含めることもできません。)どちらかでなければなりません。)

他の制限があるため、コードが本当に奇妙になります。ヘッダーに固執する。実際にそれらを好きになることを学びます(クラスができることのニースの簡単な概要を提供するため)。

2
bobobobo

他のすべての答えを読んだ後、C++標準のモジュールのサポートを追加する作業が進行中であることがわかりません。 C++ 0xには到達しませんが、意図は、後のテクニカルレビューで取り組むことです(新しい標準を待つのではなく、時間がかかります)。

議論されていた提案は N207 です。

悪い点は、最新のc ++ 0xコンパイラーを使用しても、それが得られないことです。待つ必要があります。それまでの間、header-onlyライブラリの定義の一意性とコンパイルのコストの間で妥協する必要があります。

Rix0rrrの一般的な回答のバリエーションを提供するには:

// foo.cph

#define INCLUDEMODE
#include "foo.cph"
#include "other.cph"
#undef INCLUDEMODE

void foo()
#if !defined(INCLUDEMODE)
{
   printf("Hello World!\n");
}
#else
;
#endif

void bar()
#if !defined(INCLUDEMODE)
{
    foo();
}
#else
;
#endif

私はこれをお勧めしませんが、この構造はコンテンツの繰り返しの削除を示していますが、繰り返しの繰り返しが犠牲になっていると思います。コピーパスタが簡単になると思いますか?それは本当に美徳ではありません。

この性質の他のすべてのトリックと同様に、関数の本体を変更するには、その関数を含むファイルを含むすべてのファイルの再コンパイルが必要です。非常に慎重な自動化ツールはこれを部分的に回避できますが、ソースファイルを解析して確認する必要があり、違いがない場合は出力を書き換えないように慎重に構築する必要があります。

他の読者の場合:この形式のガードを含めるのに数分を費やしましたが、良いものは思いつきませんでした。コメント?

2
Phil Miller

ヘッダーファイルなしで開発することは完全に可能です。ソースファイルを直接含めることができます。

#include "MyModule.c"

これに関する主な問題は、循環依存関係の1つです(つまり、Cで呼び出す前に関数を宣言する必要があります)。コードを完全にトップダウンで設計する場合、これは問題ではありませんが、慣れていない場合は、この種の設計パターンに頭を包むのに時間がかかることがあります。

循環的な依存関係が絶対に必要な場合は、宣言専用のファイルを作成し、他のすべてのファイルの前に含めることを検討してください。これは少し不便ですが、すべてのCファイルにヘッダーを付けるよりも汚染が少ないです。

私は現在、私の主要なプロジェクトの1つでこの方法を使用して開発しています。私が経験した利点の内訳は次のとおりです。

  • ソースツリーのファイル汚染がはるかに少なくなります。
  • ビルド時間の短縮。 (コンパイラによって生成されるオブジェクトファイルは、main.oのみです)
  • 単純なmakeファイル。 (コンパイラによって生成されるオブジェクトファイルは、main.oのみです)
  • 「きれいにする」必要はありません。すべてのビルドは「クリーン」です。
  • ボイラープレートコードの削減。少ないコード=潜在的なバグが少なくなります。

Gish(Cryptic Seaのゲーム、Edmund McMillen)は、独自のソースコード内でこの手法のバリエーションを使用していることを発見しました。

1

私の知る限り、いいえ。ヘッダーは、言語としてのC++に固有の部分です。前方宣言により、コンパイラーは関数全体を含めることなく、コンパイルされたオブジェクト/関数への関数ポインターを含めることができることを忘れないでください(関数をインラインで宣言することで回避できます(コンパイラーのように感じる場合)。

あなたが本当に、本当に、本当にヘッダーを作るのが嫌いなら、代わりにそれらを自動生成するPerlスクリプトを書いてください。私はそれをお勧めするかどうかはわかりません。

1
mikek

ヘッダーなしで実行できます。しかし、なぜ長年にわたって専門家によって開発されてきたベストプラクティスを慎重に解決することを避けようと努力するのでしょうか。

基本を書いたとき、私は行番号がとても好きでした。しかし、私はそれらをC++に詰め込もうとは考えません。それはC++の方法ではないからです。ヘッダーについても同じことが言えます...そして、他の答えがすべての理由を説明していると確信しています。

0
Scott Langham

実用的目的がない場合、それは不可能です。技術的には、はい、できます。しかし、率直に言って、それは言語の乱用であり、言語に適応する必要があります。または、C#のようなものに移動します。

0
Paul Nathan

ヘッダーファイルを使用することをお勧めします。しばらくすると、ヘッダーファイルが大きくなります。ファイルが1つしかないほうが簡単ですが、コーディングが悪い場合もあります。

これらのことのいくつかは、また気まずく、あなたが目を合わせてより多くを得ることができます。

例として、ポインタについて、値/参照によるパラメータの受け渡しなどを考えます。

私にとって、ヘッダーファイルallow-meを使用すると、プロジェクトを適切に構造化できます。

0
Katanagashi

ヘッダーファイルが良いことであることを認識してください。これらは、コードが別のユーザーにどのように見えるかを、実際に操作を実行する方法の実装から分離します。

誰かのコードを使用する場合、クラスのメソッドが何であるかを確認するために、すべての実装を調べなければなりません。コードがどうするかではなく、コードが何をするかを気にかけます。

0
Richard

すべての依存関数が依存関係の後にコンパイルされるように関数を慎重にレイアウトできますが、Nilsが示唆するように、それは実用的ではありません。

Catalin(欠落している発音区別符号を許す)は、ヘッダーファイルでメソッドを定義するより実用的な代替案も提案しました。これは、ほとんどの場合に実際に機能します。特に、ヘッダーファイルにガードが含まれていて、それらが1回だけ含まれていることを確認する場合。

個人的には、ヘッダーファイルと関数の宣言は、新しいコードを「頭の中に入れる」ためにはるかに望ましいと思いますが、それは個人的な好みだと思います...

0
John Weldon

これは複製のおかげで「復活」しました...

いずれにせよ、ヘッダーの概念は価値のあるものです。つまり、実装の詳細からインターフェースを分離します。ヘッダーには、クラス/メソッドの使用方法の概要が記載されており、使用方法は説明されていません。

欠点は、ヘッダー内の詳細と必要なすべての回避策です。これらは私がそれらを見る主な問題です:

  • 依存関係の生成。ヘッダーが変更された場合、このヘッダーを含むソースファイルを再コンパイルする必要があります。問題はもちろん、どのソースファイルが実際にそれを使用しているかを調べることです。 「クリーン」ビルドが実行されると、多くの場合、後で使用するために何らかの種類の依存関係ツリーに情報をキャッシュする必要があります。

  • ガードを含める。わかりました、私たちはすべてこれらを書く方法を知っていますが、完璧なシステムではそれは必要ないでしょう。

  • プライベートの詳細。クラス内では、プライベートの詳細をヘッダーに入れる必要があります。はい、コンパイラはクラスの「サイズ」を知る必要がありますが、完全なシステムでは、後の段階でこれをバインドできます。これにより、依存関係を非表示にしたいという理由だけで実装が1つしかない場合でも、pImplなどのすべての種類の回避策が実行され、抽象基本クラスが使用されます。

完璧なシステムは

  • 個別のクラス定義と宣言
  • これら2つの間の明確なバインドにより、コンパイラはクラス宣言とその定義がどこにあるかを知り、クラスのサイズを知ることができます。
  • プリプロセッサusing classではなく、#includeを宣言します。コンパイラは、クラスを見つける場所を知っています。 「クラスの使用」が完了したら、クラス名を修飾せずに使用できます。

Dがそれをどのように行うかを知りたいと思います。

ヘッダーなしでC++を使用できるかどうかについては、抽象基本クラスと標準ライブラリには必要ありません。それを除けば、おそらくあなたは望んでいないかもしれませんが、あなたはそれらなしでうまくいくことができます。

0
CashCow