質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

関数

関数(ファンクション・メソッド・サブルーチンとも呼ばれる)は、はプログラムのコードの一部であり、ある特定のタスクを処理するように設計されたものです。

Q&A

解決済

2回答

11815閲覧

c言語 構造体メンバに関数ポインタ

mightyMask

総合スコア143

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

関数

関数(ファンクション・メソッド・サブルーチンとも呼ばれる)は、はプログラムのコードの一部であり、ある特定のタスクを処理するように設計されたものです。

0グッド

1クリップ

投稿2017/05/27 08:55

例えば、int型のリスト構造を作りたいとします。

c

1/* 01.c */ 2struct int_node; 3typedef struct int_node int_NODE; 4typedef int_NODE* int_LIST; 5 6int_LIST int_LIST_add_first( int_LIST list, int n ); 7 8typedef struct int_node { 9 int data; 10 int_LIST next; 11}; 12 13int_LIST int_LIST_add_first( int_LIST list, int n ) { 14 /* 処理を記述 */ 15}

普通ならこんな感じで書くわけですが、int_node のメンバに関数ポインタを持たせることで、もっと便利になりそうです。
構造体のサイズが大きくなってしまって、メモリ効率的には非効率ですが、そこは気にしないことにします。

c

1/* 02.c */ 2struct int_node; 3typedef struct int_node int_NODE; 4typedef int_NODE* int_LIST; 5 6int_LIST int_LIST_init(); 7static int_LIST int_LIST_add_first( int_LIST, int ); 8 9typedef struct int_node { 10 int data; 11 int_LIST next; 12 int_LIST (*add_first)( int_LIST, int ); 13}; 14 15int_LIST int_LIST_init( void ) { 16 int_LIST list = (int_LIST)malloc( sizeof(int_NODE) ); 17 list->add_first = int_LIST_add_first; 18 return list; 19} 20 21static int_LIST int_LIST_add_first( int_LIST list, int n ) { 22 /* 処理を記述 */ 23} 24

これで、使用側では以下のように使うことになります。

c

1int main( void ) { 2 int_LIST il = int_LIST_init(); 3 il->add_first( il, 1 ); 4}

ここで、il->add_first( il, 1 );と書くのではなく、il->add_first(1);と書けるようになれば、使いやすくなります。
いろいろ考えましたが、うまいやり方が思い浮かびません。


自分なりに考えたのは、

c

1#define HOGE(object,function,arg) ( typeof(object)##_LIST_##function(object, arg) )

こんな感じのマクロを書きます。01.c にこれを加えると、
HOGE(il,add_first,1);と書くと、( int_LIST_add_first(il,1) );と展開されます。
でもあんまりすっきりした書き方にならないです。
引数が2つ以上の場合はうまくいかないですし。
il->add_first(1)int_LIST_add_first(il, 1)と展開させるのが理想なのですが。
c言語だと、thisポインタも、メンバ関数もないため、すごく難しいです。
上手いやり方はないでしょうか。
コンパイラは gcc version 5.3.0 (GCC) です。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

GCCのCでは無理だと思います。

Cでは関数に渡される情報はその引数以外に存在しません。それが元々なんだったのか評価されたあとは失われます。たとえば、il->add_first(1);は次のような評価です。

  1. まず->の部分は糖衣構文ですので、(*il).add_first(1)と解釈されます。
  2. 始めにilが評価され、そのポインタが示す【アドレス】になります。(*【アドレス】).add_first(1)
  3. 次に*【アドレス】が評価され、ポインタが示す先の【構造体】になります。【構造体】.add_first(1)
  4. 次に【構造体】.add_firstが評価され、その構造体のメンバーである【関数】になります。【関数】(1)

この4の時点で【関数】がどうやって求められたかの情報は無くなっており、ilに辿り着くことはできません。ilの情報を内包した関数でも作らない限り(後述)、ilの情報を関数内で使うことは不可能です。

では、どうするのかというと、特別なthisとしてアクセスできるように言語を拡張するしかありません。でも、これはぶっちゃけていうとC++でやっていることです。他にもPythonのように第一引数に常に入るようにするという拡張もあるかも知れません。その場合でも演算子は->以外に必要ですし、やはり、言語自体の拡張するしかありません。

結局はC++を再発明することになって終わると思います。それなら、初めからC++を使えばいいとなりますし、C++を使うのなら、STLを使った方がいいで終わってしまうと思います。


C++を使えばいいという夢の無い話で終わってしまいそうですが、Macのみで使えるとっておきの方法があります。MacのClangではAppleの拡張でブロックという機能があります。

C

1#include <Block.h> 2#include <stdio.h> 3#include <stdlib.h> 4struct int_node; 5typedef struct int_node int_NODE; 6typedef int_NODE *int_LIST; 7typedef int_LIST (^AddFirst)(int); 8 9int_LIST int_LIST_init(); 10static int_LIST int_LIST_add_first(int_LIST, int); 11 12struct int_node { 13 int data; 14 int_LIST next; 15 AddFirst add_first; 16}; 17 18int_LIST int_LIST_init(void) 19{ 20 __block int_LIST list = (int_LIST)malloc(sizeof(int_NODE)); 21 list->add_first = Block_copy(^(int n) { 22 return int_LIST_add_first(list, n); 23 }); 24 return list; 25} 26 27static int_LIST int_LIST_add_first(int_LIST list, int n) 28{ 29 int_LIST prev_list = int_LIST_init(); 30 prev_list->data = n; 31 prev_list->next = list; 32 return prev_list; 33} 34 35int main(void) 36{ 37 int_LIST il = int_LIST_init(); 38 il->data = 2; 39 il->next = NULL; 40 il = il->add_first(3); 41 il = il->add_first(5); 42 il = il->add_first(7); 43 for (int_LIST p = il; p != NULL; p = p->next) { 44 printf("%d\n", p->data); 45 } 46 return 0; 47}

ブロックはクロージャーを実現できる機能です。GCCにも関数内で関数をつくる拡張がありますが、こちらはクロージャーを作れません。初期化時に関数を作成できるだけでは不十分です。その関数に作成したリストの情報を一緒に含めなければなりません。それにはクロージャーの仕組みが必須になります。

C++のラムダ式でも同様のことが可能です。

C++

1#include <cstdio> 2#include <cstdlib> 3#include <functional> 4struct int_node; 5typedef struct int_node int_NODE; 6typedef int_NODE *int_LIST; 7 8int_LIST int_LIST_init(); 9static int_LIST int_LIST_add_first(int_LIST, int); 10 11struct int_node { 12 int data; 13 int_LIST next; 14 std::function<int_LIST(int)> add_first; 15}; 16 17int_LIST int_LIST_init() 18{ 19 int_LIST list = new int_NODE(); 20 list->add_first = [list](int n) { return int_LIST_add_first(list, n); }; 21 return list; 22} 23 24static int_LIST int_LIST_add_first(int_LIST list, int n) 25{ 26 int_LIST prev_list = int_LIST_init(); 27 prev_list->data = n; 28 prev_list->next = list; 29 return prev_list; 30} 31 32int main() 33{ 34 int_LIST il = int_LIST_init(); 35 il->data = 2; 36 il->next = NULL; 37 il = il->add_first(3); 38 il = il->add_first(5); 39 il = il->add_first(7); 40 for (int_LIST p = il; p != NULL; p = p->next) { 41 printf("%d\n", p->data); 42 } 43 return 0; 44}

素直に、クラスを使った方が(というより、C++のstructはすでにクラスですが)簡単だと思います。

投稿2017/05/27 10:28

編集2017/05/27 10:31
raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

こんにちは。

C++ってil->int_LIST_add_first(1)と書いたらint_LIST_add_first(il, 1)と内部的に展開するのですよ。
マクロを使ってこれと同じ展開はできないので、無理ではないかと思います。

オリジナルなプリプロセッサを開発して、このような展開を行う方法は考えられますが、その超拡張版がC++のようなものですから車輪の再発明を頑張るのもどうかと感じます。


linuxのカーネルで、よく似た構造を見た記憶があります。
確かデバイス・ディスクリプタだったと思います。メンバ関数を動的に切り替えたいからそのような構造だったのだと思いますが、ソース・コードを見てもどの関数が設定されないるのか分からないので、なかなか解読困難な構造でした。

投稿2017/05/27 09:20

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問