Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

わかりにくい関数ポインタを返す関数

スポンサードリンク

f:id:fa11enprince:20200624215158j:plain
関数ポインタって変な文法ですよねってお話です。
もちろんポインタの文法もいろいろツッコミどころはあるのですが
(このせいでポインタよくわからんな人が続出)。
例えば次の宣言はぱっと見すぐにわかる人はC言語マスターです。

void (*func(const char *str)) ();

これはvoidを返すのではなく、
void (*)()という関数ポインタを返す、
funcという名前のconst char *strを引数にとる関数です(;'∀')

(void (*)()) func(const char *str);

のような書き方が許されていれば誰も混乱しないのに……

コードを書いてみます。
gccでコンパイルしてみました(VC++だともっとincludeしないとダメかも)

#include <stdio.h>

void ps1()
{
    puts("banana");
}
void ps2()
{
    puts("apple");
}
void ps3()
{
    puts("orange");
}
void (*func(const char *str)) ()
{
    if (strcmp(str, "ps1") == 0)
    {
        return ps1;
    }
    else if (strcmp(str, "ps2") == 0)
    {
        return ps2;
    }
    else if (strcmp(str, "ps3") == 0)
    {
        return ps3;
    }
    return NULL;
}

int main()
{
    func("ps1")();
    // こう書いても同じ
    //void (*test)() = func("ps1");
    //(*test)();
    
    func("ps2")();
    func("ps3")();
    return 0;
}

実行結果

banana
apple
orange

これtypedefを使うともっとましになるよってことで書いてみます。

#include <stdio.h>

typedef void (*FUNC_PS)();

void ps1()
{
    puts("banana");
}
void ps2()
{
    puts("apple");
}
void ps3()
{
    puts("orange");
}
FUNC_PS func(const char *str)
{
    if (strcmp(str, "ps1") == 0)
    {
        return ps1;
    }
    else if (strcmp(str, "ps2") == 0)
    {
        return ps2;
    }
    else if (strcmp(str, "ps3") == 0)
    {
        return ps3;
    }
    return NULL;
}

int main()
{
    func("ps1")();
    func("ps2")();
    func("ps3")();
    return 0;
}

多少マシになりましたね。
ただ、見て分かる通り、ここでも問題が、
typedefがわかりにくい……
なぜ、typedef FUNC_PS void(*)();ではないのだ……
これならだれもこんがらがらないのに……

じゃあ、そんな書き方できるのあるよ!!ってことでC++11ならば

#include <cstring>
#include <cstdio>
#include <cstdlib>

using FUNC_PS = void (*)();

void ps1()
{
    std::puts("banana");
}
void ps2()
{
    std::puts("apple");
}
void ps3()
{
    std::puts("orange");
}
FUNC_PS func(const char *str)
{
    if (std::strcmp(str, "ps1") == 0)
    {
        return ps1;
    }
    else if (std::strcmp(str, "ps2") == 0)
    {
        return ps2;
    }
    else if (std::strcmp(str, "ps3") == 0)
    {
        return ps3;
    }
    return NULL;
}

int main()
{
    func("ps1")();
    func("ps2")();
    func("ps3")();
    return 0;
}

これならわかりやすい!!
ということでこれならOKでしょう。

ただし、C++11使うならstd::functionを使いましょう。

#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <functional>

void ps1()
{
    std::puts("banana");
}
void ps2()
{
    std::puts("apple");
}
void ps3()
{
    std::puts("orange");
}
std::function<void()> func(const char *str)
{
    if (std::strcmp(str, "ps1") == 0)
    {
        return ps1;
    }
    else if (std::strcmp(str, "ps2") == 0)
    {
        return ps2;
    }
    else if (std::strcmp(str, "ps3") == 0)
    {
        return ps3;
    }
    return NULL;
}

int main()
{
    func("ps1")();
    func("ps2")();
    func("ps3")();
    return 0;
}

世の中にはもっと変態的な宣言がありまして
signalが有名ですね

void (*signal(int sig, void (*func)(int))) (int);

Σ(゚д゚lll)!!!?
もうチョイわかりやすく書くと…

void (*signal(
    int sig, /* シグナル番号 */
    void (*func) (int) /* シグナルハンドラ */
)) (int);

これは
void (*)(int)を返す
signalという名前のintとvoid (*func)(int)を引数にとる関数です。
typedefしてあるとなんてことはない…

typedef void (*sighandler_t)(int);
sighandler_t signal(int signo, sighandler_t func);

typedefの文法がもっとましならよかったのに…

using sighandler_t = void (*)(int);
sighandler_t signal(int signo, sighandler_t func);

だったらよかったのに……
というかこれならだれも混乱せずに読めるのに

(void (*)(int)) signal(int sig, void (*func)(int));