CからC++のクラスを利用する方法

July 12,2017 12:03 AM
Category:[C/C++]
Tag:[C/C++]
Permalink

CからC++のクラスを利用する方法を紹介します。

1.問題点

下記のC++プログラムを作りました。

foo.h

class Foo {
public:
    Foo();
    int abc(void);
};

foo.cc

#include "foo.h"
#include <iostream>
 
Foo::Foo() {}
 
int Foo::abc(void) {
    std::cout << "OK\n";
}

このC++プログラムをCのプログラムからアクセスしたいのですが、方法が分かりません。

ということで、CからC++のクラスを利用する方法を紹介します。

2.CからC++のクラスを利用する

やり方は色々あると思いますが、ここではC++プログラムにCからアクセスするためのラッパーを追加する方法を紹介します。

foo.h

#ifdef __cplusplus
extern "C" {
#endif
 
class Foo {
public:
    Foo();
    int abc(void);
};
 
void construct();
void execute();
void destruct();
 
#ifdef __cplusplus
}
#endif

ヘッダfoo.hには、ラッパー関数construct()、execute()、destruct()を宣言し、全体を「extern "C"」で括ります。「extern "C"」については「extern "C"の意味」をご覧ください。

foo.cc

#include "foo.h"
#include <iostream>
 
Foo::Foo() {}
 
int Foo::abc(void) {
    std::cout << "OK\n";
}
 
Foo *foo;
void construct() {
    foo = new Foo() ;
}
 
void execute() {
    foo->abc();
}
 
void destruct() {
    delete(foo);
}

foo.ccでは各ラッパー関数からクラスにアクセスするよう実装します。

Cのプログラム(test.c)は次のように書きます。

test.c

extern construct();
extern execute();
extern destruct();
 
int main() {
    construct();
    execute();
    destruct();
}

C++のラッパー関数はextern宣言します。

ビルドは、C++のコンパイルはg++、Cのコンパイルはgcc、リンケージはg++で行います。

% gcc -c test.c
% g++ -c foo.cc
% g++ -o a.out foo.o test.o

その他、上記のサンプルではラッパー内でnewを実施しないと実行時にセグメンテーションフォルトになったため、もしかしたらコンストラクタの実装が必要かもしれません。

Comments [0] | Trackbacks [0]

extern "C"の意味

July 4,2017 12:03 AM
Category:[C/C++]
Tag:[C/C++]
Permalink

extern "C"の意味について調べてみました。

1.はじめに

CプログラムからC++の関数を起動することを確認するため、下記のサンプルを作りました。

foo.h

class Foo {
private:
    int number;
public:
    Foo(int num);
    int print(void);
};
 
void construct();
void foo_print();
void destruct();

foo.cpp

#include "foo.h"
#include <iostream>
 
Foo::Foo(int num) {
    number = num;
}
 
int Foo::print(void) {
    std::cout << number << '\n';
}
 
Foo *foo;
void construct() {
    foo = new Foo(100) ;
}
 
void foo_print() {
    foo->print();
}
 
void destruct() {
    delete(foo);
}

test.c

extern foo_print();
extern construct();
extern destruct();
 
int main() {
    construct();
    foo_print();
    destruct();
}

このサンプルをビルドすると、リンケージで下記のエラーが発生します。

% gcc -c test.c
% g++ -c foo.cpp
% g++ -o a.out test.o foo.o
test.o: 関数 `main' 内:
test.c:(.text+0xa): `construct' に対する定義されていない参照です
test.c:(.text+0x14): `foo_print' に対する定義されていない参照です
test.c:(.text+0x1e): `destruct' に対する定義されていない参照です
collect2: エラー: ld はステータス 1 で終了しました

2.extern "C"の意味

リンケージエラーの説明の前に、サンプルコードfoo.cppをコンパイルのみ実行し、nmコマンドでシンボルリストを表示してみます。

% g++ -c foo.cpp
% nm foo.o
00000000000000db t _GLOBAL__sub_I__ZN3FooC2Ei
000000000000009e t _Z41__static_initialization_and_destruction_0ii
0000000000000089 T _Z8destructv
0000000000000043 T _Z9constructv
0000000000000074 T _Z9foo_printv
0000000000000008 b _ZL3foo
0000000000000016 T _ZN3Foo5printEv
0000000000000000 T _ZN3FooC1Ei
0000000000000000 T _ZN3FooC2Ei
                 U _ZNSolsEi
                 U _ZNSt8ios_base4InitC1Ev
                 U _ZNSt8ios_base4InitD1Ev
                 U _ZSt4cout
0000000000000000 b _ZStL8__ioinit
                 U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c
                 U _ZdlPv
                 U _Znwm
                 U __cxa_atexit
                 U __dso_handle

次にfoo.hを下記のように「extern "C"」を付与してコンパイルし、nmコマンドでシンボルリストを表示してみます。

foo.h

#ifdef __cplusplus
extern "C" {
#endif
 
class Foo {
private:
    int number;
public:
    Foo(int num);
    int print(void);
};
 
void construct();
void foo_print();
void destruct();
 
#ifdef __cplusplus
}
#endif
% g++ -c foo.cpp
% nm foo.o
00000000000000db t _GLOBAL__sub_I__ZN3FooC2Ei
000000000000009e t _Z41__static_initialization_and_destruction_0ii
0000000000000008 b _ZL3foo
0000000000000016 T _ZN3Foo5printEv
0000000000000000 T _ZN3FooC1Ei
0000000000000000 T _ZN3FooC2Ei
                 U _ZNSolsEi
                 U _ZNSt8ios_base4InitC1Ev
                 U _ZNSt8ios_base4InitD1Ev
                 U _ZSt4cout
0000000000000000 b _ZStL8__ioinit
                 U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c
                 U _ZdlPv
                 U _Znwm
                 U __cxa_atexit
                 U __dso_handle
0000000000000043 T construct
0000000000000089 T destruct
0000000000000074 T foo_print

2つのシンボルリストの違いは下記です。

extern "C"なし

0000000000000089 T _Z8destructv
0000000000000043 T _Z9constructv
0000000000000074 T _Z9foo_printv

extern "C"あり

0000000000000043 T construct
0000000000000089 T destruct
0000000000000074 T foo_print

extern "C"なしのシンボルは"_Z8"や"_Z9"という文字が追加されています。

これは「マングリング」と呼ばれ、C++では関数のオーバーロードや、複数のネームスペースに存在する同名の変数を見分けるようにするため、このようなマングリングを行う必要があるようです。

が、C言語にはマングリングという概念はありません。

test.cのオブジェクトファイルのシンボルは下記のようになっています。

% nm test.o
                 U construct
                 U destruct
                 U foo_print
0000000000000000 T main

つまり「extern "C"」なしでコンパイルを行うと、リンケージでCプログラムが期待するシンボル名construct/destruct/foo_printがみつからず、冒頭のリンケージエラーになってしまいます。

「extern "C"」ありでコンパイルを行うと、マングリングが不要なもの(ここではconstruct/destruct/foo_print)はマングリングされないため、Cプログラムとのリンケージが成功します。

Comments [0] | Trackbacks [0]

C++のコンストラクタで「no matching function for call to」というエラーになる場合の対処

June 28,2017 12:03 AM
Category:[C/C++]
Tag:[C++]
Permalink

C++のコンストラクタで「no matching function for call to」というエラーになる場合の対処について紹介します。

1.問題点

C++で下記の簡単なサンプルを作りました。

foo.h

class Foo {
private:
    int number;
public:
    Foo(int num);
    int print(void);
};

foo.cpp

#include "foo.h"
#include <iostream>
 
Foo::Foo(int num) {
    number = num;
}
 
int Foo::print(void) {
    std::cout << number << '\n';
}

test.cpp

#include "foo.h"
 
int main() {
    Foo f(100);
    f.print();
}

実行結果

% g++ foo.cpp test.cpp
% ./a.out
100

で、本題です。

上記のサンプルに、Fooクラスを継承するBarクラスを追加してみました。

bar.h

#include "foo.h"
 
class Bar: public Foo {
public:
    Bar(int num);
    int print(void);
};

bar.cpp

#include "bar.h"
#include <iostream>
 
Bar::Bar(int num) {
    number = num;
}
 
int Bar::print(void) {
    std::cout << number << '\n';
}

foo.hのメンバ変数numberは子クラスからもアクセスするのでprivateからprotectedに変更。

foo.h

class Foo {
protected:
    int number;
public:
    Foo(int num);
    int print(void);
};

test.cppからのインクルードやmain()のクラス型はFooからBarにそれぞれ変更します。

test.cpp

#include "bar.h"
 
int main() {
    Bar f(100);
    f.print();
}

ビルドを実行すると、コンパイルで「no matching function for call to 'Foo::Foo()'」というエラーが発生します。

% g++ foo.cpp bar.cpp test.cpp
bar.cpp: コンストラクタ 'Bar::Bar(int)' 内:
bar.cpp:4:17: エラー: no matching function for call to 'Foo::Foo()'
Bar::Bar(int num) {
^

2.原因

コンパイルエラーの原因はつぎのいずれかになります。

  • Fooクラスに引数が空のコンストラクタが定義されていない
  • Barクラスのコンストラクタに継承するコンストラクタが定義されていない

ひとつめの原因の解説ですが、C++の子クラスでコンストラクタを起動すると、先に親のコンストラクタが呼ばれるようになっています。

前述のBarクラスのコンストラクタ定義では、Barのコンストラクタ起動前に、親クラスの引数なしのコンストラクタが起動されるのですが、Fooクラスには引数なしのコンストラクタが定義されていないため、「no matching function for call to 'Foo::Foo()'(Foo::Foo()の関数呼び出しが正常に行えない)」というエラーに遭遇してしまったようです。

あるいはBarクラスのコンストラクタを変更して、継承する親コンストラクタを定義すれば、親クラスに引数なしのコンストラクタを定義する必要はありません。

ということで、それぞれの原因に対する対処方法を下記に示します。

ちなみにC++にはJava言語の「super」に該当するものはありません。

3.対処方法1:Fooクラスに引数が空のコンストラクタを定義

ひとつめの対処方法は、赤字で示すように、Fooクラスに引数が空のコンストラクタを定義することです。

foo.h

class Foo {
protected:
    int number;
public:
    Foo();
    Foo(int num);
    int print(void);
};

foo.cpp

#include "foo.h"
#include <iostream>
 
Foo::Foo() {}
 
Foo::Foo(int num) {
    number = num;
}
 
int Foo::print(void) {
    std::cout << number << '\n';
}

実行結果

% g++ foo.cpp bar.cpp test.cpp
% ./a.out
100

4.対処方法2:Barクラスのコンストラクタに継承するコンストラクタを定義

もうひとつの対処方法は、Barクラスのコンストラクタ定義に、赤字で示すように継承するコンストラクタを定義することです。

bar.cpp

#include "bar.h"
#include <iostream>
 
Bar::Bar(int num): Foo(num) {
    number = num;
}
 
int Bar::print(void) {
    std::cout << number << '\n';
}

実行結果

% g++ foo.cpp bar.cpp test.cpp
% ./a.out
100

継承するコンストラクタのパラメータは変数名のみ記述すればOKです。型の記述は必要ありません。

Comments [0] | Trackbacks [0]
 1  |  2  |  3  |  4  |  5  | All pages