ページ 11

ライブラリのようなものをつくりたい

Posted: 2012年2月23日(木) 18:47
by MoNoQLoREATOR
ある一つのヘッダファイルをインクルードすることで、そのヘッダファイルに定義されている様々な関数(やクラス等)を扱うことができるようにしたいと考えています。

正確には、「関数(やクラス等)が定義されているヘッダファイル」へのインクルード文が記述されている「ある一つのヘッダファイル」を読み込むことで実現させたいという状況です。

しかし以下のようにすると『main.obj : error LNK2005: "void __cdecl funcB(void)" (?funcB@@YAXXZ) は既に hoge.obj で定義されています』というエラーが出てしまいます。


main.cpp

コード:

#include "head.h"

void main(){
	funcA();
	funcB();
	funcC();
}
hoge.cpp

コード:

#include "head.h"
//関数定義など
head.h

コード:

#include "A.h"
#include "B.h"
#include "C.h"
A.h

コード:

#pragma once

#include "B.h"

void funcA(){}
B.h

コード:

#pragma once

void funcB(){}
C.h

コード:

#pragma once

void funcC(){}

どうしたらよいのでしょうか。
ご教授よろしくお願い致します。

Re: ライブラリのようなものをつくりたい

Posted: 2012年2月23日(木) 18:59
by nullptr
・・・・・?

コード:

#pragma once
 
void funcB(){}
で中身を定義していますが、hoge.cppでは何を定義しているのですか?

Re: ライブラリのようなものをつくりたい

Posted: 2012年2月23日(木) 19:24
by MoNoQLoREATOR
>>loweさん
「エラーが出てしまうケースとして、このようなケースがありますが、どうすればよいでしょう?」と聞くためだけにhoge.cppをつくったので、実際には何も定義していません。(hoge.cppでhead.hをインクルードしなければこのエラーは起こりません。つまりこのエラーは、複数のファイルでhead.hがインクルードされた場合に発生するようです。)

Re: ライブラリのようなものをつくりたい

Posted: 2012年2月23日(木) 19:35
by softya(ソフト屋)
例えば

コード:

#pragma once
 
#include "B.h"
 
void funcA(){}
だと funcA()の実体の定義ということになるので、このヘッダを複数のcppにインクルードすると多重定義になります。

[補足]
ヘッダだけインクルードして、プログラムを組み込んでしまう方法としては
1.本当にライブラリにしてしまう。
2.定義か参照か切り分ければ良いので龍神録のようにGLOBAL_INSTANCEの定義有無で切り分ける。
3.クラスの宣言だけを行いインスタンスをヘッダでは生成しない。つまりヘッダには単なる関数の定義は書かない。

Re: ライブラリのようなものをつくりたい

Posted: 2012年2月23日(木) 20:50
by たかぎ
ややバッドノウハウ的ではありますが...
ヘッダファイルの中で定義する関数は、すべてインライン関数か関数テンプレートにしましょう。
それで解決するはずです。

Re: ライブラリのようなものをつくりたい

Posted: 2012年2月24日(金) 17:32
by MoNoQLoREATOR
>>softya(ソフト屋)さん
>>たかぎさん

返信ありがとうございます。
とりあえずソフト屋さんの2番目の方法で解決してみることにしました。
しかし、以下のようにしてみても、同じエラーが出ました。

main.cpp

コード:

#define FW_FUNCTION
#include "head.h"

void main(){
	funcA();
	funcB();
	funcC();
}
hoge.cpp

コード:

#include "head.h"
//関数定義など
head.h

コード:

#ifdef FW_FUNCTION
#define FUNCTION
#else
#define FUNCTION extern
#endif

#include "A.h"
#include "B.h"
#include "C.h"
A.h

コード:

#pragma once

#include "B.h"

FUNCTION void funcA(){}
B.h

コード:

#pragma once

FUNCTION void funcB(){}
C.h

コード:

#pragma once

FUNCTION void funcC(){}

Re: ライブラリのようなものをつくりたい

Posted: 2012年2月24日(金) 17:52
by softya(ソフト屋)
ちがいますよ。
私の書き方たが悪かったですかね。

コード:

#ifdef FW_FUNCTION
// 実体
void funcA(){
	//	実際の関数の処理
}
#else
//プロトタイプ
void funcA();
#endif
って事です。

Re: ライブラリのようなものをつくりたい

Posted: 2012年2月24日(金) 18:23
by たかぎ
FUNCTIONマクロは何の意味もありませんね。
そうではなく...

B.h

コード:

#ifndef B_H
#define B_H

inlin void funcB(){}

#endif
のようにしましょうということです。

Re: ライブラリのようなものをつくりたい

Posted: 2012年2月24日(金) 19:28
by nullptr
MoNoQLoREATOR さんが書きました:>>loweさん
「エラーが出てしまうケースとして、このようなケースがありますが、どうすればよいでしょう?」と聞くためだけにhoge.cppをつくったので、実際には何も定義していません。
ああ、そうでしたか。すみません。

たかぎさんがインライン化を勧めているのでそれでもいいと思いますが、そもそもライブラリにするならヘッダファイルに実体を作ってしまうこと自体あまりよろしく無いと思います。
ヘッダファイルに実体を作らなければいい話ですし、hoge.cppをサンプルに用意したという事はヘッダに全て詰め込まなくてはいけないという状況でも無いと思いますから。


あと、単なる誤記でしょうが

コード:

#ifndef B_H
#define B_H
 
inline void funcB(){}
 
#endif
ですね

Re: ライブラリのようなものをつくりたい

Posted: 2012年2月24日(金) 22:48
by MoNoQLoREATOR
>>softya(ソフト屋)さん
>>たかぎさん
>>loweさん
返信ありがとうございます。

たかぎさんにインライン関数を進めて頂いておりますが、あまり使う気分になりません。インライン関数を調べてみたのですが、#defineを使用したときのように、該当部分がコンパイル前に置き換えられるてしまうのですよね?なんだか非効率的な気がします。

それと、私は「ライブラリのようでライブラリでない物」を作る予定です。ライブラリと言うと堅苦しいイメージがあるので・・・。気分の問題です。

さて、下記のように修正してみるとエラーは出なくなりました。
解決しました。本当にありがとうございました。

main.cpp

コード:

#define FW_FUNCTION
#include "head.h"

void main(){
	funcA();
	funcB();
	funcC();
}
hoge.cpp

コード:

#include "head.h"
//関数定義など
head.h

コード:

#include "A.h"
#include "B.h"
#include "C.h"
A.h

コード:

#pragma once
#include "B.h"

#ifdef FW_FUNCTION
void funcA(){}
#else
void funcA();
#endif
B.h

コード:

#pragma once
#ifdef FW_FUNCTION
void funcB(){}
#else
void funcB();
#endif
C.h

コード:

#pragma once
#ifdef FW_FUNCTION
void funcC(){}
#else
void funcC();
#endif

Re: ライブラリのようなものをつくりたい

Posted: 2012年2月24日(金) 23:06
by beatle
MoNoQLoREATOR さんが書きました: たかぎさんにインライン関数を進めて頂いておりますが、あまり使う気分になりません。インライン関数を調べてみたのですが、#defineを使用したときのように、該当部分がコンパイル前に置き換えられるてしまうのですよね?なんだか非効率的な気がします。
使いたくないなら使わないでまったく問題は有りません.
ただ,インライン関数がいつも非効率かというと,まったくそうでは有りません.

例えば,

コード:

class A
{
    int x_, y_;
public:
    A() : x_(), y_() {}
    int get_x() const { return x_; }
};
というクラスを考えます.
クラスの中で関数を定義しますと,暗黙にインライン化を要請することになりますので,get_x関数はほとんどの場合インライン化されます.
もし,get_xがインライン化されない場合,get_xの呼び出しは例えば
  1. 戻り番地をスタックに積む
  2. thisポインタをスタックに積む
  3. get_xにジャンプ(ジャンプするので,CPUのパイプラインはフラッシュされてしまう)
  4. thisポインタにx_のオフセットを加算して,そのアドレスからデータをロード
  5. ロードした値をeaxレジスタに格納
  6. get_xから戻る
などという手順を踏むことになります.get_xがインライン化されれば,この手順のうち1, 2, 3, 6の手順をすっ飛ばすことができます.結構効率が良くなります.

インライン化されるとコードサイズが肥大化してしまう,という懸念はまさにその通りです.インライン化はコードサイズを少し増やす代わりに,実行速度を上げることができる可能性がある技術です.

Re: ライブラリのようなものをつくりたい

Posted: 2012年2月25日(土) 06:01
by たかぎ
beatle さんが書きました:インライン化されるとコードサイズが肥大化してしまう,という懸念はまさにその通りです.インライン化はコードサイズを少し増やす代わりに,実行速度を上げることができる可能性がある技術です.
実はそうでもないんですよ。
引数のコピーや関数のコール&リターンのコードが、関数の実体のサイズを上回るときはもちろん、そうでない場合でもインライン関数のほうが小さくなることがあります。

というのは、関数を呼び出すと、その関数から例外が送出される可能性を考慮しないといけないわけですが、インライン関数の場合は中が丸見えですので、例外が送出される可能性がまったくなければそのことがコンパイラにわかります。
結果として、例外にからんだコードが生成されなくても済むようになります。

あるいは、関数の枠を超えた最適化ができますので、極端な場合、関数の処理をコンパイル時に解決してしまうことさえできるのです。

もっとも、何でもかんでもインライン関数にすればよいというわけではありません。
テンプレートと組み合わせれば、インラインではない関数もヘッダファイルに定義を記述することが可能になります。
ヘッダファイルに定義を全部入れることの便利さは、Boost C++ Librariesなどのテンプレートライブラリを使ったことがある人なら容易に理解できるはずです。

Re: ライブラリのようなものをつくりたい

Posted: 2012年2月25日(土) 07:19
by MoNoQLoREATOR
>>beatleさん
>>たかぎさん

非効率的だという言い方には語弊がありましたね。
お言葉をお借りしますと、「コードの肥大化」が気に入らないというのが理由です。
関数があまりにも短い場合はインライン関数にする予定ですよ。