C言語で関数ポインタを使ってカプセル化に挑戦してみた

 要はデータと手続きを一緒にすりゃいいんでしょ簡単簡単みたいなテキトーな考えに基づいて、構造体のメンバに関数ポインタを代入するだけのカプセル化(笑)に挑戦してみました。

 さて、どんな題材にしようか迷ったあげく、C言語でもっとも面倒な処理の一つである文字列処理、それを簡単に扱うためのイミュータブルな文字列クラス(のような構造体)を作成することにしました。
 定義するのは、

  • 構造体
  • メソッド群
  • 生成/破棄関数

で、生成関数内部でヒープ領域に構造体を確保すると同時に、メンバ関数を挿すポインタに関数ポインタを代入します。アプリケーション側はこれを使ってオブジェクトを作成し、必要なくなったら破棄関数を呼び出してメモリから解放します。メソッドはconcat/replaceの2つを定義することにします。それぞれ文字列を結合する、置換するメソッドです。メモリコピーでありがちなバッファオーバーフローとか気にせずに文字列をばんばん切り貼りできるようにします。

 ということで書いた。
 構造体のメンバに関数ポインタを納めているだけなので、メソッドの呼び出し時にオブジェクト自身を明示的に渡す必要があります。メソッドの第1引数がオブジェクト自身だとか、なんだかPythonみたいだ。また、破棄関数を呼び出し忘れたり何度も読んだりするとmalloc/free同様強制終了したりします。

実行結果

=== string_t.concat
hello, world!, length = 13
t = 'hello, world!hello, world!'
=== string_t.replace
v = '
<html>
    <div id="bar">
    </div>
</html>
'

ソース

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

// immutable string class
typedef struct string_t
{
    char *p;
    size_t len;
    
    struct string_t* (*concat)(struct string_t*, struct string_t*);
    struct string_t* (*replace)(struct string_t*, struct string_t*, struct string_t*);
    char* (*raw)(struct string_t*);
    size_t (*size)(struct string_t*);
    
} string_t;

// methods
string_t*
init_string_obj(char*);

void
del_string_obj(string_t*);

string_t*
string_concat(string_t* self, string_t* str)
{
    char *p;
    string_t *s;
    
    p = (char*)malloc(strlen(self->p) + strlen(str->p));
    p[0] = '\0';
    strcat(p, self->p);
    strcat(p + self->len, str->p);
    s = init_string_obj(p);
    free(p);
    
    return s;
}

string_t*
string_replace(string_t* self, string_t* pattern,  string_t* alter)
{
    int i;
    int len;
    char *p;
    string_t *s, *t;
    s = init_string_obj(self->p);
    for (i = 0; i <= s->len - pattern->len; ) {
        if (!strncmp(s->p + i, pattern->p, pattern->len)) {
            len = s->len + alter->len - pattern->len;

            p = (char*)malloc(len + 1);
            strncpy(p, s->p, i);
            strncpy(p + i, alter->p, alter->len);
            strncpy(p + i + alter->len, s->p + i + pattern->len, len - i - alter->len);
            p[len] = '\0';
           
            t = init_string_obj(p);
            del_string_obj(s);
if (p)
            free(p);
            s = t;
            
            i += pattern->len;
        } else 
            i++;
    }
    return s;
}

char*
string_raw(string_t* self)
{
    return self->p;
}
 
size_t
string_size(string_t* self)
{
    return self->len;
}

//
string_t*
init_string_obj(char* p)
{
    string_t *str;
    
    if (!p)
        return NULL;
    
    str = (string_t*)malloc(sizeof(string_t));
    if (!str)
        return NULL;
    str->p = (char*)malloc(strlen(p));
    if (!(str->p)) {
        free(str);
        return NULL;
    }
    strcpy(str->p, p);
    str->len = strlen(p);
    
    str->concat = string_concat;
    str->replace = string_replace;
    str->size = string_size;
    str->raw = string_raw;
    
    return str;
}

void
del_string_obj(string_t* p)
{
    free(p->p);
    free(p);
}


int
main()
{
    string_t *s, *t, *u, *v;
    
    printf("=== string_t.concat\n");
    s = init_string_obj("hello, world!");
    
    printf("%s, length = %u\n", s->raw(s), s->size(s));
    
    t = s->concat(s, s);
    printf("t = '%s'\n", t->raw(t));
    del_string_obj(s);
    del_string_obj(t);
    
    printf("=== string_t.replace\n");
    s = init_string_obj("\n<html>\n    <div id=\"foo\">\n    </div>\n</html>\n");
    t = init_string_obj("foo");
    u = init_string_obj("bar");
    
    v = s->replace(s, t, u);
    printf("v = '%s'\n", v->raw(v));
    
    del_string_obj(s);
    del_string_obj(t);
    del_string_obj(u);
    del_string_obj(v);
    
    return 0;
}