Entry

比較的実用的なC言語のポリモーフィズムについて(その1)

2012年09月22日

ちょっと前のエントリが,まだ「その1」でとまってるけれども,少し新しい話題。コードが長くなったので,2つのエントリに分けます。

C言語でわざわざ OOP を実践する必要もないんだけれども,知っておくと便利なパターンとして構造体を利用したポリモーフィズム的な使い方があります。あくまでも,ポリモーフィズム「的」であって,多態そのものではないんですけど,ちょっと紹介。

C言語では,C++ や Java のように言語仕様として異なる引数を持つ同じ識別子の関数を定義することができません。したがって,関数引数の多態は表現できません。しかし,継承に基づく多態なら,C言語でも似たようなことができます。

例えば,次のような例を考えます。以下は,それぞれの型に基づいてその値を保持する構造体です。ここでは,文字列型と整数型,それに配列型を定義しました。配列型は,異なる型も要素に取ることができます。まずはヘッダ。

#ifndef OBJECT_H
#define OBJECT_H

#include <stdlib.h>

enum result_ {
  SUCCESS = 0,
  FAILED  = -1,
};
typedef enum result_ result;

enum object_type_ {
  STRING,
  INTEGER,
  ARRAY,
};
typedef enum object_type_ object_type;

typedef struct object_* object;
typedef struct string_* string;
typedef struct integer_* integer;
typedef struct array_* array;

result create_string(string* s, const char* str);
result destroy_string(string s);

result create_integer(integer* n, int num);
result destroy_integer(integer n);

result create_array(array* a, size_t sz);
result destroy_array(array a);
result add_array_object(array a, object o);

void to_string(object o);

#endif  /* OBJECT_H */

次に実装。

#include <stdio.h>
#include <string.h>
#include "object.h"

typedef void (*to_string_func)(object o); 

struct object_ {
  object_type type;
  to_string_func to_s;
};
typedef struct object_ object_header;

struct string_ {
  object_header head;
  char* s;
  size_t sz;
};

struct integer_ {
  object_header head;
  int n;
};

struct array_ {
  object_header head;
  object *ary;
  size_t used;
  size_t sz;
};

static void to_str_string_(object o);
static void to_str_integer_(object o);
static void to_str_array_(object o);

result
create_string(string* s, const char* str) {
  string tmp = 0;

  tmp = (string)malloc(sizeof(struct string_));
  if (!tmp) {
    return FAILED;
  }
  tmp->head.type = STRING;
  tmp->head.to_s = to_str_string_;
  tmp->s = strdup(str);
  tmp->sz = strlen(str);
  *s = tmp;

  return SUCCESS;
}

result
destroy_string(string s) {
  if (s) {
    if (s->s) {
      free(s->s);
    }
    free(s);
  }
  return SUCCESS;
}

result
create_integer(integer* n, int num) {
  integer tmp = 0;

  tmp = (integer)malloc(sizeof(struct integer_));
  if (!tmp) {
    return FAILED;
  }
  tmp->head.type = INTEGER;
  tmp->head.to_s = to_str_integer_;
  tmp->n = num;
  *n = tmp;

  return SUCCESS;
}

result
destroy_integer(integer n) {
  if (n) {
    free(n);
  }
  return SUCCESS;
}

result
create_array(array* a, size_t sz) {
  array tmp = 0;

  tmp = (array)malloc(sizeof(struct array_));
  if (!tmp) {
    return FAILED;
  }
  tmp->head.type = ARRAY;
  tmp->head.to_s = to_str_array_;
  tmp->ary = (object*)malloc(sz * sizeof(object));
  if (!tmp->ary) {
    free(tmp);
    return FAILED;
  }
  tmp->used = 0;
  tmp->sz = sz;
  *a = tmp;
  
  return SUCCESS;
}

result
destroy_array(array a) {
  if (a) {
    if (a->ary) {
      free(a->ary);
    }
    free(a);
  }
  return SUCCESS;
}

result
add_array_object(array a, object o) {
  if (a->used > a->sz) {
    return FAILED;
  }
  a->ary[a->used] = o;
  a->used++;

  return SUCCESS;
}

void
to_string(object o) {
  return o->to_s(o);
}

/** static functions */
void
to_str_string_(object o) {
  string str = (string)o;

  printf("\'");
  if (str && str->s) {
    printf("%s", str->s);
  }
  printf("\'");
}

void
to_str_integer_(object o) {
  integer num = (integer)o;
  
  printf("%d", num->n);
}

void
to_str_array_(object o) {
  array a = (array)o;
  size_t i;
  
  printf("[");
  for (i = 0; i < a->used; i++) {
    printf("%s", i > 0 ? "," : "");
    a->ary[i]->to_s(a->ary[i]);
  }
  printf("]");
}

使うときは,こんな風に使う。

#include "object.h"

int
main(int argc, char* argv[]) {
  (void)argc, (void)argv;
  integer n1, n2, n3;
  string s1, s2;
  array a1, a2;
  
  create_integer(&n1, 10);
  create_integer(&n2, 23);
  create_integer(&n3, 45);
  create_string(&s1, "hello");
  create_string(&s2, "good-bye");
  create_array(&a1, 10);
  create_array(&a2, 10);

  add_array_object(a1, (object)n1);
  add_array_object(a1, (object)a2);
  add_array_object(a2, (object)s1);
  add_array_object(a2, (object)n2);
  add_array_object(a2, (object)s2);
  add_array_object(a1, (object)n3);
  to_string((object)a1);

  destroy_integer(n1);
  destroy_integer(n2);
  destroy_integer(n3);
  destroy_string(s1);
  destroy_string(s2);
  destroy_array(a1);
  destroy_array(a2);
  
  return 0;
}

エラー処理等々がなおざりなのは,勘弁してください。結果はこんな感じになります。

$ ./a
[10,['hello',23,'good-bye'],45]

ポイントは,array_ 構造体に異なる型の構造体を格納できることと,抽象化した object_ 構造体(基底クラスに相当する)を引数にした関数 to_string と add_array_object を定義していること。仮想関数に相当する関数ポインタを設定していることです。一見,マニアックな使い方に見えるけれども,C言語のプログラムでは,頻繁に見ることができます。こゆのは,知ってないと何をしているのかよく分からなかったりするので,ちょっと紹介してみました。

詳しい話は,また後日にでも。

Trackback
Trackback URL:
Ads
About
Search This Site
Ads
Categories
Recent Entries
Log Archive
Syndicate This Site
Info.
クリエイティブ・コモンズ・ライセンス
Movable Type 3.36
Valid XHTML 1.1!
Valid CSS!
ブログタイムズ

© 2003-2012 AIAN