私はコードを作成していて、そこに 5 つのインクルード ファイルがありました。このファイルで関数を定義していたheader files
のですが、すべての関数を個別に作成して、最後にそれらをファイルに含める必要がないことに気付きました。しかし、これは通常行われていないことがわかりました。なぜだめですか?これを行うことの特定の欠点はありますか?
3 に答える
質問には間違った仮定があるため、これは本当の答えではありません。
しかし、私はこれが通常行われないことを見てきました。
本当じゃない。これは一般的な方法です。良い例はffmpeg.hです。ヘッダーは、広範なライブラリのフロントエンドです。
長いコンパイル時間の議論は偽物です。今日、システムは非常に高速です。これは本当に巨大なシステムにとってのみ重要ですが、私はあなたがそれらを使って作業しているとは本当に思いません。私自身、そのようなシステムに出会ったことはありません。
そして、コンパイル時間は実行時間ではありません。これは別の誤解です。
あなたの便宜のためにffmpeg.hのコード全体:
#ifndef _INCLUDE_FFMPEG_H_
#define _INCLUDE_FFMPEG_H_
#ifdef HAVE_FFMPEG
#include <avformat.h>
#endif
#include <stdio.h>
#include <stdarg.h>
/* Define a codec name/identifier for timelapse videos, so that we can
* differentiate between normal mpeg1 videos and timelapse videos.
*/
#define TIMELAPSE_CODEC "mpeg1_tl"
struct ffmpeg {
#ifdef HAVE_FFMPEG
AVFormatContext *oc;
AVStream *video_st;
AVCodecContext *c;
AVFrame *picture; /* contains default image pointers */
uint8_t *video_outbuf;
int video_outbuf_size;
void *udata; /* U & V planes for greyscale images */
int vbr; /* variable bitrate setting */
char codec[20]; /* codec name */
#else
int dummy;
#endif
};
/* Initialize FFmpeg stuff. Needs to be called before ffmpeg_open. */
void ffmpeg_init(void);
/* Open an mpeg file. This is a generic interface for opening either an mpeg1 or
* an mpeg4 video. If non-standard mpeg1 isn't supported (FFmpeg build > 4680),
* calling this function with "mpeg1" as codec results in an error. To create a
* timelapse video, use TIMELAPSE_CODEC as codec name.
*/
struct ffmpeg *ffmpeg_open(
char *ffmpeg_video_codec,
char *filename,
unsigned char *y, /* YUV420 Y plane */
unsigned char *u, /* YUV420 U plane */
unsigned char *v, /* YUV420 V plane */
int width,
int height,
int rate, /* framerate, fps */
int bps, /* bitrate; bits per second */
int vbr /* variable bitrate */
);
/* Puts the image pointed to by the picture member of struct ffmpeg. */
void ffmpeg_put_image(struct ffmpeg *);
/* Puts the image defined by u, y and v (YUV420 format). */
void ffmpeg_put_other_image(
struct ffmpeg *ffmpeg,
unsigned char *y,
unsigned char *u,
unsigned char *v
);
/* Closes the mpeg file. */
void ffmpeg_close(struct ffmpeg *);
/*Deinterlace the image. */
void ffmpeg_deinterlace(unsigned char *, int, int);
/*Setup an avcodec log handler. */
void ffmpeg_avcodec_log(void *, int, const char *, va_list);
#endif /* _INCLUDE_FFMPEG_H_ */
一部の人々は、関数を別のファイルに配置し、ヘッダーを介してそれらを含めると、プロジェクトにいくらかのオーバーヘッドが追加され、コンパイル (実行ではなく) 時間が長くなると主張しています。厳密に言えばこれは真実ですが、実際にはコンパイル時間の増加は無視できます。
この問題に対する私の見解は、関数の目的に基づいています。関連ヘッダーを持つファイルごとに単一の関数を配置することに反対しています。これは、すぐに維持するのが面倒になるからです。全員を 1 つのファイルにまとめることも良い方法だとは思いません (理由は異なりますが、維持するのも面倒です)。
私の意見では、理想的なトレードオフは関数の目的を見ることです。関数が他の場所で使用できるかどうかを自問する必要があります。つまり、これらの関数は、他のプログラムの一連の共通タスクのライブラリとして使用できますか? はいの場合、これらの関数を 1 つのファイルにグループ化する必要があります。一般的なタスクと同じ数のファイルを使用します。たとえば、あるファイルで数値積分を実行するすべての関数、別のファイルでファイル I/O を処理するすべての関数、3 番目のファイルで文字列を処理するすべての関数などです。このようにして、ライブラリは一貫しています。
最後に、特定のプログラムにとってのみ意味のあるタスクを実行する関数を、関数の同じファイルに配置しmain
ます。たとえば、一連の変数を初期化することを目的とする関数。
しかし、何よりも、アドバイスはアドバイスとして受け取る必要があります。最終的には、自分 (またはチーム) の開発を最も生産的にするアプローチを採用する必要があります。
ある種の API またはライブラリを作成していて、そのライブラリのユーザーが内部の関数に簡単にアクセスできるようにしたい場合は、「スーパー インクルード ヘッダー」を 1 つだけ作成する必要があります。Windows OS API はこの最も明白な例であり、1 つの #include で何千もの関数にアクセスできます。
ただし、そのようなライブラリを作成する場合でも、「スーパーヘッダー」には注意する必要があります。それらを回避しようとする理由は、プログラムの設計に関連しています。オブジェクト指向設計では、プログラムの残りの部分を認識したり気にしたりせずに、独自のタスクに焦点を当てた独立した自律的なモジュールを作成するように努力する必要があります。
その設計ルールの背後にある理論的根拠は、すべてのモジュールが他のモジュールに大きく依存している密結合として知られる現象を減らすことです。コンピューター サイエンスの研究 (たとえば、この研究など) では、密結合と複雑さが組み合わさると、はるかに多くのソフトウェア エラーが発生し、さらに深刻なエラーが発生することが示されています。密結合のプログラムが 1 つのモジュールでバグを取得すると、そのバグはプログラム全体にエスカレートし、災害を引き起こす可能性があります。一方、疎結合の 1 つの自律型モジュールのバグは、その特定のモジュールの障害につながるだけです。
ヘッダー ファイルをインクルードするたびに、プログラムとそのヘッダー ファイルの間に依存関係が作成されます。そのため、すべてを含む 1 つのファイルを作成するのは魅力的ですが、プロジェクト内のすべてのモジュール間に密結合が生じるため、これは避ける必要があります。また、モジュールを互いのグローバル名前空間に公開し、同一の変数名などで名前空間の競合が増える可能性があります。
密結合は、安全上の懸念は別として、プログラムをリンク/ビルドするときに非常に厄介です。密結合では、GUI ライブラリがリンクされていないなど、まったく関係のないものがあると、突然データベース モジュールが機能しなくなります。