オブジェクト指向Cを書くのは悪いですか?[閉まっている]


14

私は常にオブジェクト指向であるCでコードを書くように見えるので、ソースファイルまたは構造体を作成し、この構造体が所有する関数(メソッド)にこの構造体へのポインターを渡すものがあるとしましょう:

struct foo {
    int x;
};

struct foo* createFoo(); // mallocs foo

void destroyFoo(struct foo* foo); // frees foo and its things

これは悪い習慣ですか?「適切な方法」でCを書く方法を学ぶにはどうすればよいですか。


10
Linux(カーネル)の多くはこの方法で記述されており、実際には、仮想メソッドのディスパッチなどのオブジェクト指向のような概念をさらにエミュレートします。かなり適切だと思います。
キリアンフォス

13
彼は、本物のプログラマがあらゆる言語でFORTRANプログラムを書くことができると判断しました。」- エドポスト、1983年
ロスパターソン

4
C ++に切り替えたくない理由はありますか?好きではない部分を使用する必要はありません。
-svick

5
これは、「「オブジェクト指向」とは何ですか?」このオブジェクト指向とは呼びません。手続き的だと思います。(継承もポリモーフィズムも、状態を隠すカプセル化/能力もありません。おそらく、頭の外に出ていないOOの他の特徴を欠いているでしょう。)それが良いか悪いかは、それらのセマンティクスに依存しません。 、しかし。
jpmc26

3
@ jpmc26:あなたが言語的処方主義者なら、Alan Kayを聞いてください。彼はこの用語を発明し、それが何を意味するのかを語り、OOPはメッセージングについてすべてだと言います。あなたが言語的記述主義者であれば、ソフトウェア開発コミュニティでの用語の使用状況を調査します。Cookはそれを正確に行い、OOを主張する言語またはOOと見なされる言語の機能を分析しましたが、それらには共通点が1つあることがわかりました
ヨルグWミットタグ

回答:


24

いいえ、これは一つでもでものような規則を使用することができますが、そうすることが奨励され、悪い習慣ではありませんstruct foo *foo_new();し、void foo_free(struct foo *foo);

もちろん、コメントにあるように、適切な場合にのみこれを行ってください。のコンストラクタを使用しても意味がありませんint

接頭辞foo_は、他のライブラリの命名と衝突しないように保護するため、多くのライブラリが続く規則です。他の機能にはしばしば使用するコンベンションがありますfoo_<function>(struct foo *foo, <parameters>);。これによりstruct foo、不透明(OPAQUE)型にすることができます。

特に「サブネームスペース」に関する規約については、libcurlのドキュメントご覧curl_multi_*くださいcurl_easy_init()。最初のパラメーターが返されたときに、関数の呼び出しが一見間違っているように見えます。

さらに一般的なアプローチがあります。ANSI-Cを使用したオブジェクト指向プログラミングを参照してください。


11
「適切な場合」に常に注意してください。OOPは特効薬ではありません。
デュプリケータ

Cには、これらの関数を宣言できる名前空間がありませんか?同様にstd::string、持っていませんfoo::createか?私はCを使用しません。たぶんそれはC ++だけにあるのでしょうか?
クリスサイレフィス

@ChrisCirefice Cには名前空間がないため、多くのライブラリ作成者が関数にプレフィックスを使用しています。
レジデューム

2

悪くない、素晴らしい。オブジェクト指向プログラミングは良いことです(あなたが夢中にならない限り、あなたはあまりにも良いことをすることができます)。CはOOPに最適な言語ではありませんが、C言語を最大限に活用することを妨げるものではありません。


4
私は反対することができますが、あなたの意見は本当にによってサポートされる必要がありませんいくつかの精緻化。
デデュプリケーター

1

悪くない。多くのバグを防ぐRAIIの使用を推奨します(メモリリーク、初期化されていない変数の使用、解放後の使用など、セキュリティ上の問題を引き起こす可能性があります)。

そのため、GCCまたはClangのみでコードをコンパイルする場合(MSコンパイラーではなく)、cleanup属性を使用できます。これにより、オブジェクトが適切に破棄されます。そのようにオブジェクトを宣言する場合:

my_str __attribute__((cleanup(my_str_destructor))) ptr;

その後my_str_destructor(ptr)、ptrがスコープから外れると実行されます。関数argumentsと共に使用できないことに注意してください

また、名前空間がないmy_str_ため、メソッド名で使用することを忘れないでくださいC。他の関数名と簡単に衝突する可能性があります。


2
Afaik、RAIIは、C ++でオブジェクトのデストラクタの暗黙的な呼び出しを使用して、クリーンアップを確実にし、明示的なリソース解放呼び出しを追加する必要を回避することについてです。したがって、私があまり間違えていなければ、RAIIとCは相互に排他的です。
cmaster-モニカの復元16年

@cmaster #define使用する型名を指定すると__attribute__((cleanup(my_str_destructor)))#defineスコープ全体で暗黙的に取得されます(すべての変数宣言に追加されます)。
マーチン16

これは、a)gccを使用する場合、b)関数のローカル変数でのみ型を使用する場合、およびc)ネイキッドバージョンでのみ型を使用する場合(#defined型またはその配列へのポインターなし)に機能します。要するに、これは標準のCではなく、使用する際に多くの柔軟性が失われます。
cmaster-モニカの復元16年

私の答えで述べたように、それはclangでも機能します。
マーチン

ああ、私はそれに気づかなかった。これにより、要件は__attribute__((cleanup()))かなり準標準になるため、要件a)は大幅に緩和されます。ただし、b)とc)はまだ立っています...
cmaster-モニカを

-2

このようなコードには多くの利点がありますが、残念なことにC標準はそれを容易にするために作成されていません。コンパイラは歴史的に、標準が必要とする以上の効果的な動作保証を提供してきたため、標準Cで可能なコードよりもはるかにきれいにコードを記述できるようになりましたが、コンパイラは最近、最適化という名前でそのような保証の取り消しを開始しました。

最も注目すべきことは、多くのCコンパイラは、2つの構造体型に同じ初期シーケンスが含まれている場合、型が無関係であっても、いずれかの型へのポインターを使用してその共通シーケンスのメンバーにアクセスできることを歴史的に保証しています(ドキュメントではない場合)さらに、共通の初期シーケンスを確立するために、構造体へのすべてのポインターが同等であること。そのような動作を利用するコードは、そうでないコードよりもはるかにクリーンでタイプセーフになりますが、残念ながら、標準では共通の初期シーケンスを共有する構造を同じ方法でレイアウトする必要があるにもかかわらず、実際に使用することは禁止されています別の初期シーケンスにアクセスするための1つのタイプのポインター。

そのため、Cでオブジェクト指向コードを記述したい場合は、Cのポインター型のルールを順守するために多くのフープを飛び越えて、古いコンパイラが意図したとおりに動作するコードを生成したとしても、スリップした場合、現代のコンパイラは無意味なコードを生成します。または、コードが古いスタイルのポインタ動作をサポートするように構成されたコンパイラでのみ使用できるという要件を文書化します「-fno-strict-aliasing」)一部の人々は「-fno-strict-aliasing」を悪と見なしますが、「-fno-strict-aliasing」Cを言語であると考える方がより役立つことをお勧めします。 「標準」Cよりも大きな目的のセマンティックパワーを提供します。ただし、他の目的には重要となる可能性のある最適化を犠牲にします。

例として、従来のコンパイラでは、歴史的なコンパイラは次のコードを解釈します。

struct pair { int i1,i2; };
struct trio { int i1,i2,i3; };

void hey(struct pair *p, struct trio *t)
{
  p->i1++;
  t->i1^=1;
  p->i1--;
  t->i1^=1;
}

次の手順を順番に実行します。の最初のメンバーをインクリメントし、の最初のメンバーの*p最下位ビットを補完し*t、次にの最初のメンバーをデクリメントし、の最初のメンバーの*p最下位ビットを補完し*tます。最新のコンパイラは、異なるオブジェクトpt識別し、識別できる場合にコードが効率的になるように操作の順序を並べ替えますが、そうでない場合は動作を変更します。

もちろん、この例は意図的に工夫されており、実際には、あるタイプのポインターを使用して別のタイプの共通の初期シーケンスの一部であるメンバーにアクセスするコードが通常は機能しますが、残念ながらそのようなコードがいつ失敗するかを知る方法はありません型ベースのエイリアス解析を無効にすることを除いて、安全に使用することはまったくできません。

任意の型への2つのポインターの交換のようなことを行う関数を作成したい場合、やや不自然な例が発生します。「1990年代のC」コンパイラの大部分では、次の方法で実現できます。

void swap_pointers(void **p1, void **p2)
{
  void *temp = *p1;
  *p1 = *p2;
  *p2 = temp;
}

ただし、標準Cでは、以下を使用する必要があります。

#include "string.h"
#include "stdlib.h"
void swap_pointers2(void **p1, void **p2)
{
  void **temp = malloc(sizeof (void*));
  memcpy(temp, p1, sizeof (void*));
  memcpy(p1, p2, sizeof (void*));
  memcpy(p2, temp, sizeof (void*));
  free(temp);
}

場合*p2割り当てられたストレージに保持され、そして一時的なポインタが割り当てられたストレージに保持されていない、の有効なタイプは、*p2試みが使用する一時的なポインタの種類、およびコードとなるであろう*p2一時的なポインタと一致しなかった任意のタイプとしてタイプは未定義の動作を呼び出します。コンパイラーがそのようなことに気付く可能性は非常に低いはずですが、現代のコンパイラー哲学では、プログラマーがすべてのコストで未定義の動作を回避する必要があるため、割り当てられたストレージを使用せずに上記のコードを記述する他の安全な方法は考えられません。


Downvoters:コメントしてください。オブジェクト指向プログラミングの重要な側面は、複数の型に共通の側面を共有させ、それらの共通の側面にアクセスするためにそのような型へのポインターを使用できるようにする機能です。OPの例はそれを行いませんが、「オブジェクト指向」であることの表面をかろうじて引っ掻いています。歴史的なCコンパイラでは、ポリモーフィックコードを今日の標準Cよりもはるかに簡潔に記述することができます。したがって、Cでオブジェクト指向コードを設計するには、ターゲットとする正確な言語を決定する必要があります。人々のどの側面に同意しませんか?
-supercat

うーん...あなたは標準が与える保証が、共通の初期サブシーケンスのメンバーにきれいにアクセスすることを許可しないことをどのように示しているのでしょうか?契約行動の範囲内で最適化するための大胆な悪についてのあなたの怒りは、この時期にかかっていると思うからです?(それはです私の 2 downvotersが不快見つけたもの推測。)
デュプリケータ

OOPは必ずしも継承を必要としないため、実際には2つの構造体間の互換性はあまり問題になりません。関数ポインターを構造体に入れ、特定の方法でそれらの関数を呼び出すことで、実際のオブジェクト指向を取得できます。もちろん、このfoo_function(foo*, ...)Cの擬似OOはクラスのように見える特定のAPIスタイルにすぎませんが、抽象データ型を使用したモジュラープログラミングと呼ぶ方が適切です。
アモン

@Deduplicator:指定された例を参照してください。フィールド「i1」は両方の構造の共通の初期シーケンスのメンバーですが、コードが「struct pair *」を使用して「struct trio」の初期メンバーにアクセスしようとすると、最新のコンパイラーは失敗します。
-supercat

どの現代のCコンパイラがその例に失敗しますか?興味深いオプションが必要ですか?
デデュプリケーター

-3

次のステップは、構造体宣言を非表示にすることです。これを.hファイルに入れます:

typedef struct foo_s foo_t;

foo_t * foo_new(...);
void foo_destroy(foo_t *foo);
some_type foo_whatever(foo_t *foo, ...);
...

そして、.cファイルで:

struct foo_s {
    ...
};

6
目的によっては、次のステップになるかもしれません。それがそうであるかどうかにかかわらず、これは質問に答えようとはしません。
デュプリケータ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.