こんいちは
MS-Visual studio 2010を使っています。
classのメンバー関数の実装をinline付けないですけれども、
inlineを付けるメンバー関数(MF)の定義ファイルがclassの定義ファイル(.h ファイル)と違うファイルであれば、
MFをCALLしている処に
「MFに関して解析できない」というLINKエラーが発生します。
実例として下記の通りですが、
このような不都合はMS-Visual studio 2010より高いversionのMS-Visual studioにも発生するのでしょうか。
class CIntArray
{
private:
int* m_pnum; // 動的配列
public:
MF(); // メモリの確保が成功したか
};
// 下記のメンバー関数が上記class定義本体の存在ファイルにしなければ、LINK エラー!
inline bool CIntArray:: MF()
{
return m_pnum != NULL;
}
inline メンバー関数の中身定義位置について
Re: inline メンバー関数の中身定義位置について
さっそくご返答ありがとうございます
> 宣言時に戻り値の型(bool)が抜けています。指定するとよいかと思います。
元ソースの中では boolが抜けていません。
掲示板に書く時のミスです。 ---御免なさい!
> 宣言時に戻り値の型(bool)が抜けています。指定するとよいかと思います。
元ソースの中では boolが抜けていません。
掲示板に書く時のミスです。 ---御免なさい!
Re: inline メンバー関数の中身定義位置について
MS-Visual studioはわかりませんが、gcc 4.8.1では、
inlineを付けるメンバー関数(MF)の定義ファイルがclassの定義ファイル(.h ファイル)と違うファイルであり、かつリンクエラーにならない場合が存在しました。
よって、gcc 4.8.1では「下記のメンバー関数が上記class定義本体の存在ファイルにしなければ、LINK エラー!」という命題は偽です。
また、Microsoft Visual C++ 2008 Express Editionでも、
同様にinlineを付けるメンバー関数(MF)の定義ファイルがclassの定義ファイル(.h ファイル)と違うファイルであり、かつリンクエラーにならない場合が存在し、
「下記のメンバー関数が上記class定義本体の存在ファイルにしなければ、LINK エラー!」という命題は偽です。
hoge.h hogehoge.cpp
コンパイルした結果
しかし、CIntArray::MF()を定義するファイルとそれを使うファイルを別の翻訳単位にすると、
gcc 4.8.1およびMicrosoft Visual C++ 2008 Express Editionでリンクエラーになりました。
これは、おそらくinline指定された関数は独立したサブルーチンにするのではなくその関数が「呼び出された」場所にその関数の処理のコードを埋め込むことを想定しているため、
独立した「関数」のコードとしては出力されないためであると思います。
inlineを付けるメンバー関数(MF)の定義ファイルがclassの定義ファイル(.h ファイル)と違うファイルであり、かつリンクエラーにならない場合が存在しました。
よって、gcc 4.8.1では「下記のメンバー関数が上記class定義本体の存在ファイルにしなければ、LINK エラー!」という命題は偽です。
また、Microsoft Visual C++ 2008 Express Editionでも、
同様にinlineを付けるメンバー関数(MF)の定義ファイルがclassの定義ファイル(.h ファイル)と違うファイルであり、かつリンクエラーにならない場合が存在し、
「下記のメンバー関数が上記class定義本体の存在ファイルにしなければ、LINK エラー!」という命題は偽です。
hoge.h hogehoge.cpp
#include "hoge.h"
const void* NULL = (void*)0;
// 下記のメンバー関数が上記class定義本体の存在ファイルにしなければ、LINK エラー!
inline bool CIntArray:: MF()
{
return m_pnum != NULL;
}
int main(void) {
CIntArray cia;
cia.MF();
return 0;
}
YUKI.N>md5sum hoge.h hogehoge.cpp
13a47862aed25eeb80c9a3ac2eb6c955 *hoge.h
edf5c2bcc0afcf7139dbccde7ee4c4c3 *hogehoge.cpp
YUKI.N>g++ -Wall -Wextra -o hoge.exe hogehoge.cpp
YUKI.N>hoge.exe
YUKI.N>
gcc 4.8.1およびMicrosoft Visual C++ 2008 Express Editionでリンクエラーになりました。
YUKI.N>g++ --version
g++ (GCC) 4.8.1
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
YUKI.N>cat raw1_1.h
class CIntArray
{
private:
int* m_pnum; // 動的配列
public:
bool MF(); // メモリの確保が成功したか
};
YUKI.N>cat raw1_2.cpp
#include "raw1_1.h"
const void* NULL = (void*)0;
inline bool CIntArray::MF()
{
return m_pnum != NULL;
}
#ifdef SELF_TEST
int main(void) {
CIntArray cia;
cia.MF();
return 0;
}
#endif
YUKI.N>cat raw1_3.cpp
#include "raw1_1.h"
int main(void) {
CIntArray cia;
cia.MF();
return 0;
}
YUKI.N>g++ -DSELF_TEST -o raw1_2.exe raw1_2.cpp
YUKI.N>g++ -c -o raw1_2.o raw1_2.cpp
YUKI.N>g++ -o raw1_3.exe raw1_3.cpp raw1_2.o
C:\Users\kota\AppData\Local\Temp\cc832a2T.o:raw1_3.cpp:(.text+0x15): undefined r
eference to `CIntArray::MF()'
collect2.exe: error: ld returned 1 exit status
YUKI.N>objdump -d raw1_2.o
raw1_2.o: file format pe-i386
YUKI.N>objdump -t -T -r -R raw1_2.o
raw1_2.o: file format pe-i386
objdump: raw1_2.o: not a dynamic object
SYMBOL TABLE:
[ 0](sec -2)(fl 0x00)(ty 0)(scl 103) (nx 1) 0x00000000 raw1_2.cpp
File
[ 2](sec 1)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .text
AUX scnlen 0x0 nreloc 0 nlnno 0
[ 4](sec 2)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .data
AUX scnlen 0x0 nreloc 0 nlnno 0
[ 6](sec 3)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .bss
AUX scnlen 0x4 nreloc 0 nlnno 0
[ 8](sec 4)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .rdata$zzz
AUX scnlen 0x11 nreloc 0 nlnno 0
[ 10](sec 3)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 _NULL
DYNAMIC SYMBOL TABLE:
no symbols
objdump: raw1_2.o: Invalid operation
YUKI.N>
独立した「関数」のコードとしては出力されないためであると思います。
- 添付ファイル
-
- test.zip
- VCのプロジェクト(リンクエラーにならない)
- (45.11 KiB) ダウンロード数: 88 回
-
- test_error.zip
- VCのプロジェクト(リンクエラーになる)
- (9.84 KiB) ダウンロード数: 95 回
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)
Re: inline メンバー関数の中身定義位置について
以下の状況でしょうか。リンクエラーが発生しますね。
test.h test.cpp main.cpp 下記にも記載されていますが、ヘッダに実装しないといけないようです。2013でも同様です。
関数インライン展開の問題 - MSDN - Microsoft
test.h test.cpp main.cpp 下記にも記載されていますが、ヘッダに実装しないといけないようです。2013でも同様です。
関数インライン展開の問題 - MSDN - Microsoft
Re: inline メンバー関数の中身定義位置について
横から失礼いたします。
私も最近inlineについて調べていたので、その過程でわかったことを書いてみようと思います。
(間違いがあったらごめんなさい)
C++11の規格によれば、外部結合を持つinline指定された宣言が、ある翻訳単位に現れる場合、
その関数の宣言が現れるすべての翻訳単位でinlineをつけて宣言する必要があります。
従って、クラスの定義とメンバ関数の定義を別ファイルに分ける場合は、
クラスの定義内の宣言、メンバ関数の定義のどちらにもinlineを指定する必要があると考えられます。
また、inline指定された関数が使用されるすべての翻訳単位にその関数の定義が含まれる必要があります。
従って、クラス定義内の関数宣言にinlineを指定し、それをインクルードして利用するだけでは規格に違反したプログラムになってしまいます。
解決策としては、クラス定義内に関数定義を書いてしまうのが一番手軽だと思います(そのような関数は自動的にinline関数になります)。
can110さんの仰るように、同じファイル内でクラス定義の外にinline指定された関数定義を書く場合についても問題ありません。
質問者様の最初のコードは、
* MFの定義にinlineが指定されているので、MFの定義をクラス定義と分ける場合はクラス定義内の宣言にinlineが必要
* inline関数であるということは、MFを使用する翻訳単位(たとえばmain.cppなど)にも定義が必要
という点において問題が生じていたと考えられます。
どうしてもクラス定義とメンバ関数の定義を別ファイルに分けて書く必要がある場合、
* 関数の宣言と定義両方にinlineを指定する
* 関数を使用する翻訳単位全てに関数の定義を取り込む
必要があると考えられます。
しかし、クラス定義内の宣言にinlineを指定してしまうと、そのクラス定義を含むヘッダファイルを取り込む翻訳単位内でインライン関数を定義する必要があります。
つまり、結局、クラス定義を含むヘッダファイルに関数定義を書いた場合と同じことになってしまいます。
array.h
array_inline_def.h
main.cpp
array.hを使う複数の翻訳単位が存在した場合に定義が重複しそうですが、inline関数については重複が認められています。
インライン関数に関する問題はコンパイル時にではなくリンク時に判明する場合があるため非常にわかりにくいと感じます。
(参考にした規格)
http://www.open-std.org/jtc1/sc22/wg21/ ... /n3242.pdf
7.1.2 Function specifiers
私も最近inlineについて調べていたので、その過程でわかったことを書いてみようと思います。
(間違いがあったらごめんなさい)
C++11の規格によれば、外部結合を持つinline指定された宣言が、ある翻訳単位に現れる場合、
その関数の宣言が現れるすべての翻訳単位でinlineをつけて宣言する必要があります。
従って、クラスの定義とメンバ関数の定義を別ファイルに分ける場合は、
クラスの定義内の宣言、メンバ関数の定義のどちらにもinlineを指定する必要があると考えられます。
また、inline指定された関数が使用されるすべての翻訳単位にその関数の定義が含まれる必要があります。
従って、クラス定義内の関数宣言にinlineを指定し、それをインクルードして利用するだけでは規格に違反したプログラムになってしまいます。
解決策としては、クラス定義内に関数定義を書いてしまうのが一番手軽だと思います(そのような関数は自動的にinline関数になります)。
can110さんの仰るように、同じファイル内でクラス定義の外にinline指定された関数定義を書く場合についても問題ありません。
質問者様の最初のコードは、
* MFの定義にinlineが指定されているので、MFの定義をクラス定義と分ける場合はクラス定義内の宣言にinlineが必要
* inline関数であるということは、MFを使用する翻訳単位(たとえばmain.cppなど)にも定義が必要
という点において問題が生じていたと考えられます。
どうしてもクラス定義とメンバ関数の定義を別ファイルに分けて書く必要がある場合、
* 関数の宣言と定義両方にinlineを指定する
* 関数を使用する翻訳単位全てに関数の定義を取り込む
必要があると考えられます。
しかし、クラス定義内の宣言にinlineを指定してしまうと、そのクラス定義を含むヘッダファイルを取り込む翻訳単位内でインライン関数を定義する必要があります。
つまり、結局、クラス定義を含むヘッダファイルに関数定義を書いた場合と同じことになってしまいます。
array.h
#ifndef ARRAY_H
#define ARRAY_H
class CIntArray
{
private:
int* m_pnum; // 動的配列
public:
inline bool MF(); // メモリの確保が成功したか
};
#endif
#ifndef ARRAY_INLINE_DEF_H
#define ARRAY_INLINE_DEF_H
#include "array.h"
inline bool CIntArray::MF()
{
return m_pnum != nullptr;
}
#endif
#include "array.h"
// インライン関数の定義を取り込む
#include "array_inline_def.h"
int main()
{
CIntArray intArray;
// MFを使うのでこの翻訳単位にMFの定義が必要
intArray.MF();
}
インライン関数に関する問題はコンパイル時にではなくリンク時に判明する場合があるため非常にわかりにくいと感じます。
(参考にした規格)
http://www.open-std.org/jtc1/sc22/wg21/ ... /n3242.pdf
7.1.2 Function specifiers
Re: inline メンバー関数の中身定義位置について
最初の質問者(すずき)でございます。
皆さま丁寧なご説明本当にありがとうございます。
★ can110様のMS VS 2015での実験形態と実験結果私の経験とまったく同じです。
★ みけCAT 様の実験は新しい発見ですね、そのようなやり方で問題ないとは知りませんでした。
★ userE様の総括は非常啓発的、指針になります。
最後にもう一度御礼を申し上げます。
皆さま丁寧なご説明本当にありがとうございます。
★ can110様のMS VS 2015での実験形態と実験結果私の経験とまったく同じです。
★ みけCAT 様の実験は新しい発見ですね、そのようなやり方で問題ないとは知りませんでした。
★ userE様の総括は非常啓発的、指針になります。
最後にもう一度御礼を申し上げます。