Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.36%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

18回答

128475閲覧

【C++】なぜヘッダと実装はわけるべきなのでしょうか(.hに実装を書くことは邪道か)

NUNU_E64

総合スコア63

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

8グッド

32クリップ

投稿2015/06/04 09:24

編集2015/06/04 15:13

私はC++歴3年の学生趣味プログラマーです。
「C++はなぜヘッダと実装を分けなくてはならないのか/そもそも本当に分けなければならないのか」という質問です。

C++といえば、ヘッダー部と実装部を.hファイルと.cppファイルに分けることが一般的とされている言語ですが、
これは同じオブジェクト指向言語のC#やJavaにはない特徴です。

そのせいでC++使いたちは今日もcppファイルとhファイルを行ったり来たりしながらコーディングする羽目になっています。(そしてVS使いはF12とCtrl+-を得意気に連打しています。)

私にとってもそれが当たり前になって久しいですが、
時々C++を学び始めたばかりの後輩から「なぜヘッダファイルに実装を書いてはならないのか」「なぜC++は二度も同じコードを書くことを強いるのか」と質問を受けます。
私はそのたびに「実装の隠蔽化」とか「循環参照の危険が云々」とか「そもそもそういう言語なんだ」とか「コード構造の可読性」とか「未来の自分が困るんだよ」とか言ってお茶を濁してきましたが、
自分自身どうも納得しきれていません。

例えば、個人開発で、循環参照の問題やコンパイル時間の問題がないようなコード構造であれば、
ヘッダに実装を書くことに果たして問題はあるのでしょうか?

私はどうやって後輩たちを納得させ、C#ライクなC++コードを書きたがる彼らを説得したらよいでしょうか。

意見を聞かせてもらえれば嬉しいです。

参考にしたサイト一部
ヘッダファイルの謎(3)
プログラミングメモ - C++ のソースをヘッダと実装に分ける理由とか
C++ でのビルド時間を短縮するいくつかの方法

関係がありそうなキーワード
・隠蔽化、循環参照、テンプレート、inline、コンパイル

追記(15/06/04 23:34):
・「第三者利用」が問題なら「ライブラリとしての公開やチーム開発などコードの再利用がない個人開発」という前提になればヘッダに実装してしまっても構わないことになりますがいいのでしょうか。
・「コンパイル時間」が問題ならマシンスペックの向上で将来的に解決されるレベルの問題であり、あくまで"現時点では"ヘッダファイルに実装を書くのは好ましくない、という話になってしまいますがそういう理解でよろしいのでしょうか。

・結局、二度書きの手間に対して十分なメリットが確保できるか否かという話になり、好みや自己責任の話になってしまうのではと危惧しています。C++とはどんな言語なのか、なかなか思想が見えてこなくて理解が及ばないです。レイヤーの低さや速度に特徴があるが、開発効率ではC#に劣る古き業務用言語ということなのでしょうか?(あえて極端な言い方をしています)

・(なお、私自身はヘッダで実装を書くことはないです。そこそこの規模のコードを書いたりしますしヘッダと実装は分けないとやってらんないです。自分が書いたコードを読んで構造を思い出す作業は苦行ですのでそれが不要な時点で恩恵は強く感じています。ですのでこれは主観の話ではなく言語思想,言語仕様の話として回答いただければ幸いです。)

kamokamo, afroscript, fa11enprince, fermat, LouiS0616, ikuwow, pebble8888, suittizihou👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答18

0

MUSTな理由としては参考リンクの
プログラミングメモ - C++ のソースをヘッダと実装に分ける理由とか
でもちょっとだけ言及されていますが、実装とヘッダ(型、インターフェース、定数定義)をちゃんと分ければ実装のソースを公開ぜずにビルド済みの(静的/動的)ライブラリとヘッダだけ提供すれば第3者が利用することができます。例えばWindowsのSDKがそうですね。
それにプラグインSDKを公開しているようなソフト、例えば画像処理ソフトのAdobe Photoshopとか、統合3DソフトのAutodesk Mayaなどがあります。最近は機能拡張はJavaScriptとかPythonを組み込んだソフトも多いですが、スピードが求められる世界ではまだC/C++も健在です。
あとは商用ミドルウェア(ライブラリ提供)でもこの用件を満たさないと、ソースを公開しないでクライアントに使ってもらうことができませんね。

C/C++はコンパイルするといわゆるネイティブコード(機械語+α)を出力しますが、デバッグ用途で出力されたものを除けば、これに型情報は含まれません(structとかtypedefとか、enumの値の意味とか)。型情報は別のファイルで、それがC/C++ではヘッダファイルという位置づけです。Javaではコンパイル後はクラスファイル、C#ではアセンブリという名前ですが、これらは型情報も保持しています。だからヘッダファイルは必要ないのです。これにはデメリットもあります。型情報だけのせいではないですがネイティブコードと比べ、逆コンパイルされるとかなり元に近いソースが復元できてしまいます。

MAYな理由はNUNU_E64さんのおっしゃる通り、循環参照やコンパイル時間です。納得しきれてないということですが、それはヘッダと実装をちゃんと分けた結果痛い目にあわなかった証拠かもしれません。

参考までに大規模C++ソフトウェアデザインという本を紹介しておきます。例えばコンパイル時間ですが、この本にはビルドに1週間かかるようになってしまった話が載っています。概説は「一戸建て住宅と超高層ビルでは建築に必要な技能や資格がまったく異なる」というような話から始まります。コンパイル時間の問題は言ってみれば高層ビルで深刻になる問題です。なので個人開発でしたらそこまで気にしなくてもよいと思います。しかしもし将来後輩たちと高層ビルに取り組むのを見据えているのなら、いまのうち気を使っておいても損ではありません。

投稿2015/06/04 12:17

編集2015/06/04 12:22
sharow

総合スコア1151

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

短くてつまらない答え:C++言語仕様としてそのように定義されており、違反するプログラムの動作結果は全く保証されないから。

C++では、関数や変数の「宣言」と「定義」は厳密に区別されます。プログラム全体を通じて同じ「宣言」が何回出現しても良いですが、同じ「定義」(この質問の文脈では"実装"に相当)は1回だけしか存在してはなりません。これは**One Definition Rule(ODR)**とよばれ、同じ定義を複数含むプログラムはODR違反により未定義動作、つまりプログラムの実行結果が意味不明な出力となったり、コンパイルエラーになったりと、何が起こるかは全くわかりません。

ヘッダファイルに「定義」が書いてある場合、そのヘッダファイルをincludeするすべてのコンパイル単位(cppファイル)に同一の「定義」が複製されて登場します。これらのコンパイルされたファイルをリンクして1つのプログラムを生成するとき、そのプログラムには同一の「定義」が複数含まれることになります。これは先のODR違反となるため、正しく動作する保証のないプログラムが出来上がります。

このルールの例外として、テンプレート(template)定義や、インライン関数(inline)定義はヘッダファイルに書いてもOKです。何がインライン関数となるかについては、少々ややこしいルールがあるので下記も参考にしてください。

lang

1// (複数cppファイルからincludeされるヘッダファイルと仮定) 2 3class Foo { 4 int m1, m2, m3; 5public: 6 // ここではメンバ関数の宣言のみ 7 int getM1(); 8 int getM2(); 9 10 // OK: クラス定義内でメンバ関数を定義した場合、暗黙にインライン関数となる 11 int getM3() { 12 return m3; 13 } 14}; 15 16// OK: クラス定義外で明示的にinlineキーワード指定すればインライン関数となる 17inline int Foo::getM1() { 18 return m1; 19} 20 21// NG:クラス定義外でinlineキーワードがなければ非インライン関数となる 22int Foo::getM2() { 23 return m2; 24}

ちなみに、ODR違反になるか否かは「インライン関数として扱われるか」という観点のみが関与します。インライン関数が実際にインライン展開されるかどうかは、ODRの議論とは無関係です。inlineキーワードはコンパイラへの最適化ヒント情報という側面ももり、こちらは単にコンパイラによる最適化性能の話です。最近のまともなコンパイラであれば、非インライン関数であってもインライン展開を行ったり、インライン関数であっても通常の関数呼び出しになったりします。

投稿2015/06/06 01:56

編集2015/06/09 12:57
yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

NUNU_E64

2015/06/08 04:13

回答ありがとうございます。ODRの話は知りませんでした。 ODR違反に対してコンパイラが対応する必要がない(未定義動作)というのも興味深いです。 言語仕様として明確に定められているのですね。 つまり、Get/Setのようなごく短いコードであってもヘッダファイルに実装(定義)するのは、正しく動作する保証のないコードを生んでいるということなのでしょうか。
yohhoy

2015/06/08 14:42

Get/Setのような単純メンバ関数では、ヘッダファイルに直接定義が書かれることが多いですね。回答追記した通り、"暗黙にinline関数"となる場合はヘッダファイルに書いてもOKです。
NUNU_E64

2015/06/09 02:09

追記ありがとうございます。 「クラス定義内でメンバ関数を定義した場合、暗黙にinline関数となる」というのは失念してました。 明示的/暗黙問わず、inline関数が実際に展開されるかはプログラマは関知できないので、inline展開されそうなごく短い関数以外はクラス定義内であってもODR違反を起こすため書くべきではない、という結論でしょうかね。
yohhoy

2015/06/09 12:58

少し誤解があるようなので追記しました。「inline関数が実際に展開されるか」どうかは、ここでの議論とは相関がありません。
NUNU_E64

2015/06/10 15:12

追記ありがとうございます。 なるのど、理解しました。
guest

0

ヘッダとソースに分ける最大の理由は分割コンパイルのためではないでしょうか。

極端な例ですが、以下のようなコードがあったとして

main.cpp

lang

1#include <iostream> 2#include "func.h" 3 4int main() 5{ 6 std::cout << func(1, 2) << std::endl; 7 return 0; 8}

func.h

lang

1inline int func(int a, int b) 2{ 3 return a + b; 4}

func 関数の中身をちょっと変更しただけでも main.cpp をコンパイルし直す必要がありますが、main.cpp は iostream もインクルードしているので、実際には 17555 行ものコードのコンパイルが必要です(-E はプリプロセスされたコードを出力するオプション)。

$ g++ -E main.cpp | wc -l 17555

そこで func.h を次のようにヘッダとソースに分割して、

func.h

lang

1int func(int a, int b);

func.cpp

lang

1int func(int a, int b) 2{ 3 return a + b; 4}

さらに main.cpp と func.cpp を分割コンパイルすれば、

$ g++ -c main.cpp $ g++ -c func.cpp $ g++ main.o func.o

この後 func 関数の中身を修正する必要があったとしても main.cpp をコンパイルし直す必要がありません。

$ g++ -c func.cpp $ g++ main.o func.o

ヘッダに実装を書いていたがために、func 関数を少しいじるのに 17555 行ものコードのコンパイルが必要でしたが、ヘッダとソースを分けたことで、たった 4 行(func.cpp の行数)のコンパイルだけで済むようになりました(*.o のリンクが必要なので単純計算で早くなっているわけではありませんが)。

投稿2015/06/04 11:34

ngyuki

総合スコア4516

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

他の方の回答を見て、「コンパイルが長い」という表現があいまいだと思ったので実体験と具体的な数字を出したいと思います。

むかし600KB, 150ファイル程度のC++プログラムの保守をしていたのですが、5年前の最新型のPCでフルビルドが1時間半ぐらいかかっていました。

フルビルドが長い事自体は、まあ、仕方がないんですけども、問題は保守業務で発生する1~2ファイルに対する変更と、そのファイルのコンパイルと、リンクです。

まずコンパイルがですね、変更した1~2ファイルに依存しているファイルもコンパイルすることになるんですけども、これが下手な include の作りになっていると芋づる式にどんどん増えるんですよ。
何故か10分ぐらいかかったりします。

ちなみにリンクも30分ぐらいかかったりしたんですけど、これはC++のクラスの仕様の影響などが大きいので省きます。

それで、常に可能な限り、依存関係が増えないように作るようになります。
前方宣言を利用するなどは当然で、依存関係のためにクラスの構造を変えた事もあります。

正しいオブジェクト指向が好きな人からは嫌がられましたが、引換に得られたものは1回のビルドで数分~数十分の節約でした。
保守業務では、ビルドとテストを1日に何十回とやるので、毎日数時間の影響がありました。

このようにビルドの時間を現実的な長さに収める、という1点においても、実装を別のファイルに書くことには大きな意義があります。

投稿2015/06/05 00:48

lichten

総合スコア133

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

haru666

2015/06/05 06:02

正しいオブジェクト指向が好きなこととC++のcpp/hの煩雑さは無関係です。 C++の正しい扱い方が分かっていない結果なんでもかんでもincludeすることになりますが、その場合正しいオブジェクト指向からはきっと遠のいています。 C言語でもオブジェクト指向は十分できます。 lichtenさんのアプローチは正しいです。
lichten

2015/06/05 08:10

そういう話ではないんですよ。 私は依存関係(≒ビルド時間)のためだけにクラス構造を変えました。 だから私のやったことはオブジェクト指向と何も関係がないんですよ。 でもビルド時間の短縮が一番重要だったんです。 ましてやヘッダファイルに実装を書くなんて、というのがこの回答の趣旨になります。
haru666

2015/06/05 08:37

> 正しいオブジェクト指向が好きな人からは嫌がられましたが 私がしているのはそれを嫌がっている人が 正しいオブジェクト指向が好きな人 では無い、というだけの話です。 コンパイル時間が短くなるのは当然ですし、更に言えばそれをすることも正しいので加えて訂正しています。 宣言部/実装部の再分配とオブジェクト指向はたしかに完全に無関係です。 ヘッダーファイルと実装を正しく別けていくことで、ソース間の結合度合いが下がりますから、例えばソースファイルの変更でどのファイルがリコンパイルすればいいのか、コンパイラは正しく判断できるようになります。これによってビルド時間は短縮されます。 で…じゃあ無関係なのに何故オブジェクト指向としての正しさに近づくのか、という話ですが、オブジェクト指向と結合度は切っても切り離せません。ソース結合度はオブジェクト指向とは関係ありませんが、ソース結合度を下げるというのは正しいオブジェクト指向の第一歩に含まれると私は思っています。
lichten

2015/06/06 05:37

haru666 さんはC++の依存関係解消のテクニックについてご存じないようですね。 Effective C++ や C++の処方箋、という書籍を一読してもらえれば理解してもらえると思うのですが・・・・・・ やり方が汚いんですよ。何か考えがあって、クラスを作った人が嫌がるのは当然です。 もしも大規模で、ビルド時間が短くて、オブジェクト指向なC++コードを紹介してもらえるという話であれば嬉しいのですが。
haru666

2015/06/06 15:08

lichtenさんの話からだけだとどのように分離させたか分からないのでテクニック云々を言及されても私には何もアドバイスできません。
guest

0

追記に対して。

コンパイル時間を気にしないのであれば、全部ヘッダに書いてもいいですよ。
ただし 循環参照等の問題が出るので、その場合は ヘッダも作ります

私の場合は
最初はヘッダに全部かいて、動作の確認をし
循環参照等の問題が出た時、リファクタリングの時に、ヘッダと定義をわけます。

作法としては、ヘッダと実装部をわけ、pImplイデオムを使い、プライベートメンバも隠蔽化&コンパイル高速化
するのが 綺麗ですので、教育するなら 綺麗なのを教える方が良いとおもいますが。

C++でテンプレートバリバリで作ると、テンプレートはヘッダにしか書けなくなるので
望まなくても、ヘッダに大量のコードを書く事になります。

投稿2015/06/04 18:41

YukiMiyatake

総合スコア144

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ヘッダと実装を分けないでコードディングすること発想は良いことだと思いますが
ヘッダファイルにコードを記述するのはキルディだと個人的に思います。なので
「cppファイルのほうにすべてをまとめるつもりで、ヘッダファイルの依存を減らすために構成を工夫するようにコードを書けば、ワンランク上のコードが記述できる」と説得すれば良いと思います。

投稿2015/06/04 10:24

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

循環参照などで、どうしてもcppに出す必要は出てきますが、前提のとおりそれを排除するとしても

ヘッダに書くとコンパイルに時間がかかります
理由は、C言語はコンパイル時にファイルの依存関係を見てますが
実装部と分離する事で依存関係を減らしているからです

ヘッダと実装が別れている理由は、C言語はビルド時に全てのシンボルのサイズを計算する必要がありますが
今の importのような便利な機能だと、シンボル解決に何度も同じソースを解析する必要があるので
ヘッダや前方宣言を使い、1回でシンボルの解決をさせる目的だと聞いています。

現在のPCは速くなったので #importも可能なんですが、C言語は下位互換性を維持するため
#includeは消えずに残ると思います
#importの追加も 検討中だと聞きました

とりあえず現状は、ヘッダと実装を分けない場合は
コンパイルが遅くなります。

投稿2015/06/04 09:56

YukiMiyatake

総合スコア144

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

C++も時代が進み、コンパイル時処理やtemplateメタプログラミングを活用した型制約プログラミングが当たり前になってきています。

そのなかで、ヘッダーオンリーライブラリはごく普通のものとなっています。
ex.) https://github.com/bolero-MURAKAMI/Sprout

これはtemplateやconstexprといった機能を使うためにヘッダーオンリーにせざるをえない状況になっているためです。

ODRやリンケージについての指摘がありましたが、
http://fimbul.hateblo.jp/entry/2014/12/11/000123
を参考にしてください。これらはヘッダーオンリーにすることの支障にはなりません(外部リンゲージをもつ変数が必要(mutexのための変数など)な場合を除く)

コンパイル時間については
少なくともVSでは関数定義を無理してヘッダーに書かないようにする必要はなさそうだ - Qiita
以前検証したことがありますが、コンパイラがある程度コンパイル結果をキャッシュしていたりするので、多少あちこちからincludeされているヘッダーを書き換えてもそこまで時間がかからなかたりします。

もっとも、C/C++の#includeは単なるコピペに過ぎないので、コンパイル時間を増大させていることは間違いなく、ずっとmoduldeというものの導入をC++標準化委員会は議論していますが、まだ当分標準入りしそうにはありません。はやく入ってコンパイル時間がboooost!しないようになるといいなぁ。

投稿2016/09/20 15:00

yumetodo

総合スコア5852

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

NUNUさんの言っている意見の中では「そもそもそういう言語なんだ」が最も近いと言えると思います。

コンパイラの性能も向上し、コンパイラごとにやっていることも異なるので、今からする話は必ずしもそうだ、といえるものではありません。

C++では実装ファイル(.cpp)単位でオブジェクトファイルを生成します。共有したヘッダファイル(.h)の情報はC++の場合個別のオブジェクトファイルに含まれています。

C++ではインスタンス化する時、宣言部を参照してクラスサイズを計算し、サイズに基づいてメモリを確保しています。(そしてその後コンストラクタがあれば呼び出しが行われます。)後は確保したメモリアドレスを宣言部に合わせて名前解決し、実装を探してリンクしているのです。
クラスサイズの話については、以下を参照すると詳しくのっています。
https://www.microsoft.com/japan/msdn/vs_previous/visualc/techmat/feature/jangrayhood/

実装部をヘッダファイルに含めてしまうと、includeする全ての実装ファイルでその実装が取り込まれてしまいます。コンパイラの性能向上によって必ずしも最終的なアプリケーションのサイズが増えるとは言えませんが、少なくともオブジェクトファイルにはコピーされた実装が含まれることになるでしょう。

C++では実装ファイル間でインスタンスの受け渡しを行う場合、メモリのアドレスを共有しているにすぎません。クラスのメソッドが実装ファイルに存在している場合は、クラス名とメソッド名から名前解決を行い、実装部を含むオブジェクトファイルを探し、そこにジャンプしてくれるようにリンカがプログラムをビルドします。リンカのこの機能を外部参照を解決するとか表現します。

ヘッダファイルに全て書いた場合、コンパイラがどのようにプログラムを構築しているか、想像することは非常に難しくなります。インライン展開されたのか、されていないのかもよくわからなくなりますし、ビルド手順によっては思わぬバグが発生するリスクも高まります。

JavaやC#にはVMがありますから、各ファイルをコンパイルした後名前解決できれば柔軟に対応することができます。そのため、生成するオブジェクトファイルをC++と違って整理しています。Cと互換性を保つことに重きを置かれたC++とは、どのようにファイルをリンクするか、その思想から違います。コンパイラの性能が向上しても、ヘッダファイルに全ての実装を書く事はC++にとって正しい選択にはならないでしょう。

投稿2015/06/05 09:31

編集2015/06/09 06:14
haru666

総合スコア1593

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

Javaはインターフェースて形でhの役割をしてるのでは?
ヘッダーは定義ファイルで、設計図を書く場所です。
実装は設計図を元に作るものです。

2つをまとめてしまうと、そこでしか使えないものになります。
間違えではないですが、汎用性が全くなくなり、メンテの時に苦労します。

究極は、ヘッダーファイルだけを見れば、実装を見なくても、なにが出来るか
直に解るので、コードを読む必要がなくなります。

投稿2015/06/04 13:02

MasaakiIrie

総合スコア1021

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

技術的な観点からは様々なご意見出ていますので、少し違った視点から書かせていただきます。

個人ユースのプログラムではJavaなどが中心になっていくのかもしれませんが、産業系のシステム開発の現場では今後もC言語、C++が主流であり続けるだろうと思います。既存システムの運用・再利用、技術者の確保などからいっても、新しい言語が取って代わるのはそうそうあることではありません。銀行系のシステムではいまだにCOBOLが生き残っていますね。
また、C言語系ではコードチェックツールの使用を義務付ける企業も増えています。(JavaやC#の世界のことは私わかりません)
「○○システムでレベル××のワーニングは出してはいけない」というようなルールを決めて、自社のSEや外注業者などに「守りなさい」と命令するわけです。
なので、あなたや後輩さんたちが今後職業としてソフトウェア開発にかかわっていくのなら、一般的に”きれい”とみなされるコーディングスタイルを身につけておくのは得策です。

>「第三者利用」が問題なら「ライブラリとしての公開やチーム開発などコードの再利用がない個人開発」という前提になればヘッダに実装してしまっても構わないことになりますがいいのでしょうか。

「第三者利用だから」、「個人開発だから」とコーディングスタイルを切り替えることができるでしょうか?
一度身についてしまった癖(特に初心者の時についてしまった癖)をやめるのは、難しいことです。
企業で新人教育などに携わってみると、「悪い癖のついた経験者より何も知らない初心者のほうが教育しやすい」のは事実です。

もう1つ。
あなたも後輩さんも学生とのことですから、あなたが一方的に「説明して納得させ」なくてもよいのでは?
疑問を持つことはよいことですが、それ以上に「自分で考える」ことも重要です。
「なぜこういう慣習が生まれたのか」後輩さんたちとディスカッションしてみるのはいかがでしょうか?

長文、失礼しました。

投稿2015/06/22 07:55

mie

総合スコア229

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

主観になりますが、
ヘッダファイルに実装を書く件は邪道ではありません。
好きにコーディングすれば良いと思います。

NUNU_E64さんが後輩に説明している件は全てC++で
開発を進めた場合に発生しうるリスク回避の手法です。
やらなければならないのではなく、
どこまでリスクを事前に想定し回避するか、
開発に携わったプログラマが判断すべき要件です。

後輩達に納得させるには、
「ヘッダファイルに実装を書いてはならない」と教えるのではなく、
メリットとデメリットを明示し知識として教えるべきです。
分けてコーディングすることにメリットしか無いような言い方は
問題の解決にならないと思います。

投稿2015/06/12 16:05

higetarou

総合スコア57

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

最近はC++を使うことはなくなったので最新の言語仕様を追えていないので間違っているかもしれませんが…。

C++と、C#やJavaなどのヘッダーと実装がわかれていない言語との差は、

C++では、クラス定義内で実装を書かれたメソッドはすべてインライン展開される(呼び出した場所にコードを展開して埋め込まれる)

のに対して

C#やJavaでは、クラス定義内で実装を書かれたメソッド(つまりは、すべてのメソッドですが…)は、
必ずひとつの関数のみ存在し、そのメソッドを呼び出したコードがあれば、常に関数の呼び出しが起きる

ことになります。

このためC++では、ヘッダーファイル内(クラス定義)でメソッドの実装を書いてしまうと、
メソッドを呼び出したコードのすべてで(ほぼ)同じ機械語に展開され、
メソッドが大きくなればなるほど最終的な実行ファイルも大きくなることになります。
(C#やJavaではこういう問題はおきない。)

それを嫌って、うん十年前はヘッダーファイル内には簡単なメソッド以外の実装は書かなかった気がします。
現在の状況はよくわかりませんが、そんな歴史もあったということで、ご参考まで。

参考URL:http://www7b.biglobe.ne.jp/~robe/cpphtml/html02/cpp02008.html

余談ですが、C++でヘッダー内でメソッドを実装すると、機械語レベルでは
関数呼び出し(スタックのpush & call)や関数からの復帰(スタックのpop & return)が起きないので実行速度があがったりします。

投稿2015/06/09 05:32

編集2015/06/09 05:38
JunSuzukiJapan

総合スコア314

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

haru666

2015/06/09 07:17

実行ファイルはスリムになると思います 以下のサイトでC++で何故スリムにできるのかということが簡単に書かれていました https://github.com/ytomino/headmaster/wiki/Using-extern-inline ただ実際にはコンパイラの性能、最適化オプション、コーディング如何によってはしっかりインライン展開されたりしますから、難しいですね
JunSuzukiJapan

2015/06/09 10:00

参考サイトでは、なんの処理系を使っているかが書かれていないので、 いまいちはっきりしませんが、C++でテンプレートを使った場合には ほぼ確実にメソッドの実体が生成されることになるでしょうね。 テンプレートだけでは機械語を生成できるだけの情報(型など)がないので、 ソースコードのどこかでテンプレートが使われるたびにインライン展開してると 他の場所で使われる可能性を考えると効率悪いですしね。 それはそれとして、[ソースファイルに書いたメンバ関数ってインライン展開されるんだっけ?](http://moccosblue.blogspot.jp/2013/01/blog-post.html) という記事で、gccとVisual Studioのそれぞれでインライン展開されるかどうかを オプションも変えながら試しているのが割と面白く参考になりました。
haru666

2015/06/10 02:17

テンプレートを利用した結果ファイルサイズが膨れ上がっても困りますもんね。(昔はでもなんかそんなこと言われていた気がします。)メソッドの実体が生成された場合、少なくとも私の手元の環境では同じメソッドへのCALLに変換されています。 私が普段使っているのは比較的マイナーだと思われる開発環境(C++Builder)なのですが、こちらではヘッダファイルに関数を書いてインライン展開されないように調整した場合は複数ファイルからの参照で同じアドレスCALLになりますね。 builderはgccともVisual Studioとも違うので、何とも言えない感じですが。
guest

0

削除させていただきました。

投稿2016/08/21 10:30

編集2016/08/29 02:17
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

0

みなさん色々言ってます。

素晴らしい回答ばかり。

私が思うに

headerと分ける理由は読みやすい!から

javaならpublic private protected とかいろいろ

そんな感じです。

大切なことは読みやすいかどうか

それが大事です。

投稿2015/06/09 13:02

suzuya

総合スコア14

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ライブラリ化した時、ヘッダーファイルが必要になります。標準ライブラリやらランタイムライブラリを使うにも、ヘッダファイルをincludeしますよね。

投稿2015/06/04 12:15

tohshima

総合スコア374

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

分割コンパイルしてリンクすると、リンクエラーになるからです

.h ファイルは、コンパイル時には .cppファイルの一部になるので
関数の実装が有ると、.cppの毎にその関数が生成されます
リンカが多重定義としてエラーにします

例外があって
inline関数、template定義での関数等の実装は、その名前の実態が出来ないので .h で定義しなければなりません

投稿2021/08/05 18:20

Tirou

総合スコア10

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

可読性についてだけ、コメントさせて下さい
ヘッダに実装を書く場合も、宣言と実装を別けて書くことは出来ますよ
宣言やクラス定義だけ書いたヘッダの最後の方で、別のヘッダ(VCだと慣例的に*.inlとかの拡張子にすることもありますが実質ヘッダと同じ)
をインクルードして、そこに実装を書けばいいだけです

テンプレートだと基本的にソースに実装を書けないので、可読性を確保したいときには重宝する方法です

投稿2016/12/16 13:03

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問