inline メンバー関数の中身定義位置について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
すずき

inline メンバー関数の中身定義位置について

#1

投稿記事 by すずき » 9年前

こんいちは
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;
}

can110
記事: 27
登録日時: 9年前

Re: inline メンバー関数の中身定義位置について

#2

投稿記事 by can110 » 9年前

宣言時に戻り値の型(bool)が抜けています。指定するとよいかと思います。

コード:

public:
bool MF(); // メモリの確保が成功したか

すずき

Re: inline メンバー関数の中身定義位置について

#3

投稿記事 by すずき » 9年前

さっそくご返答ありがとうございます

> 宣言時に戻り値の型(bool)が抜けています。指定するとよいかと思います。

元ソースの中では boolが抜けていません。
掲示板に書く時のミスです。 ---御免なさい!

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: inline メンバー関数の中身定義位置について

#4

投稿記事 by みけCAT » 9年前

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

コード:

class CIntArray
{
private:
    int* m_pnum;   // 動的配列
  
public:
    bool MF();  // メモリの確保が成功したか
    
};
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>
しかし、CIntArray::MF()を定義するファイルとそれを使うファイルを別の翻訳単位にすると、
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>
これは、おそらくinline指定された関数は独立したサブルーチンにするのではなくその関数が「呼び出された」場所にその関数の処理のコードを埋め込むことを想定しているため、
独立した「関数」のコードとしては出力されないためであると思います。
添付ファイル
test.zip
VCのプロジェクト(リンクエラーにならない)
(45.11 KiB) ダウンロード数: 88 回
test_error.zip
VCのプロジェクト(リンクエラーになる)
(9.84 KiB) ダウンロード数: 95 回
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

can110
記事: 27
登録日時: 9年前

Re: inline メンバー関数の中身定義位置について

#5

投稿記事 by can110 » 9年前

以下の状況でしょうか。リンクエラーが発生しますね。
test.h

コード:

class CIntArray{
private:
	int* m_pnum; // 動的配列
public:
	bool MF(); // メモリの確保が成功したか
};
test.cpp

コード:

#include "test.h"
inline bool CIntArray::MF(){
	return m_pnum != 0;
}
main.cpp

コード:

#include "test.h"
int main( void){
	CIntArray a;
	a.MF();	// error LNK2019
	return 0;
}
下記にも記載されていますが、ヘッダに実装しないといけないようです。2013でも同様です。
関数インライン展開の問題 - MSDN - Microsoft

userE

Re: inline メンバー関数の中身定義位置について

#6

投稿記事 by userE » 9年前

横から失礼いたします。

私も最近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
array_inline_def.h

コード:

#ifndef ARRAY_INLINE_DEF_H
#define ARRAY_INLINE_DEF_H
#include "array.h"

inline bool CIntArray::MF()
{
    return m_pnum != nullptr;
}
#endif
main.cpp

コード:

#include "array.h"
// インライン関数の定義を取り込む
#include "array_inline_def.h"

int main()
{
    CIntArray intArray;
    
    // MFを使うのでこの翻訳単位にMFの定義が必要
    intArray.MF();
}
array.hを使う複数の翻訳単位が存在した場合に定義が重複しそうですが、inline関数については重複が認められています。

インライン関数に関する問題はコンパイル時にではなくリンク時に判明する場合があるため非常にわかりにくいと感じます。

(参考にした規格)
http://www.open-std.org/jtc1/sc22/wg21/ ... /n3242.pdf
7.1.2 Function specifiers

すずき

Re: inline メンバー関数の中身定義位置について

#7

投稿記事 by すずき » 9年前

最初の質問者(すずき)でございます。
皆さま丁寧なご説明本当にありがとうございます。

★ can110様のMS VS 2015での実験形態と実験結果私の経験とまったく同じです。

★ みけCAT 様の実験は新しい発見ですね、そのようなやり方で問題ないとは知りませんでした。

★ userE様の総括は非常啓発的、指針になります。

最後にもう一度御礼を申し上げます。

閉鎖

“C言語何でも質問掲示板” へ戻る