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

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

ただいまの
回答率

90.12%

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

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 4,849

mightyMask

score 101

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

/* 01.c */
struct int_node;
typedef struct int_node int_NODE;
typedef int_NODE* int_LIST;

int_LIST int_LIST_add_first( int_LIST list, int n );

typedef struct int_node {
    int data;
    int_LIST next;
};

int_LIST int_LIST_add_first( int_LIST list, int n ) {
    /*  処理を記述  */
}

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

/* 02.c */
struct int_node;
typedef struct int_node int_NODE;
typedef int_NODE* int_LIST;

int_LIST int_LIST_init();
static int_LIST int_LIST_add_first( int_LIST, int );

typedef struct int_node {
    int data;
    int_LIST next;
    int_LIST (*add_first)( int_LIST, int );
};

int_LIST int_LIST_init( void ) {
    int_LIST list = (int_LIST)malloc( sizeof(int_NODE) ); 
    list->add_first = int_LIST_add_first;
    return list;
}

static int_LIST int_LIST_add_first( int_LIST list, int n ) {
    /*  処理を記述  */
}


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

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


ここで、il->add_first( il, 1 );と書くのではなく、il->add_first(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) です。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+3

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の拡張でブロックという機能があります。

#include <Block.h>
#include <stdio.h>
#include <stdlib.h>
struct int_node;
typedef struct int_node int_NODE;
typedef int_NODE *int_LIST;
typedef int_LIST (^AddFirst)(int);

int_LIST int_LIST_init();
static int_LIST int_LIST_add_first(int_LIST, int);

struct int_node {
        int data;
        int_LIST next;
        AddFirst add_first;
};

int_LIST int_LIST_init(void)
{
        __block int_LIST list = (int_LIST)malloc(sizeof(int_NODE));
        list->add_first = Block_copy(^(int n) {
          return int_LIST_add_first(list, n);
        });
        return list;
}

static int_LIST int_LIST_add_first(int_LIST list, int n)
{
        int_LIST prev_list = int_LIST_init();
        prev_list->data = n;
        prev_list->next = list;
        return prev_list;
}

int main(void)
{
        int_LIST il = int_LIST_init();
        il->data = 2;
        il->next = NULL;
        il = il->add_first(3);
        il = il->add_first(5);
        il = il->add_first(7);
        for (int_LIST p = il; p != NULL; p = p->next) {
                printf("%d\n", p->data);
        }
        return 0;
}

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

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

#include <cstdio>
#include <cstdlib>
#include <functional>
struct int_node;
typedef struct int_node int_NODE;
typedef int_NODE *int_LIST;

int_LIST int_LIST_init();
static int_LIST int_LIST_add_first(int_LIST, int);

struct int_node {
        int data;
        int_LIST next;
        std::function<int_LIST(int)> add_first;
};

int_LIST int_LIST_init()
{
        int_LIST list = new int_NODE();
        list->add_first = [list](int n) { return int_LIST_add_first(list, n); };
        return list;
}

static int_LIST int_LIST_add_first(int_LIST list, int n)
{
        int_LIST prev_list = int_LIST_init();
        prev_list->data = n;
        prev_list->next = list;
        return prev_list;
}

int main()
{
        int_LIST il = int_LIST_init();
        il->data = 2;
        il->next = NULL;
        il = il->add_first(3);
        il = il->add_first(5);
        il = il->add_first(7);
        for (int_LIST p = il; p != NULL; p = p->next) {
                printf("%d\n", p->data);
        }
        return 0;
}

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

こんにちは。

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

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


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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 90.12%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る