9

クリーンなコードを作成するには、C でもオブジェクト指向の概念を使用すると便利です。私はよく、.h ファイルと .c ファイルのペアで構成されるモジュールを作成します。問題は、プライベート メンバーが C に存在しないため、モジュールのユーザーが注意する必要があることです。pimpl イディオムまたは抽象データ型の使用は問題ありませんが、いくつかのコードやファイルが追加され、より重いコード。アクセサーが必要ないときにアクセサーを使用するのは嫌いです。

これは、コンパイラが「プライベート」メンバーへの無効なアクセスについて不平を言うようにする方法を提供するアイデアです。追加のコードはわずかです。アイデアは、同じ構造を 2 回定義することですが、モジュールのユーザーのために追加の 'const' を追加します。

もちろん、「プライベート」メンバーでの書き込みは、キャストを使用して引き続き可能です。ただし、重要なのは、メモリを安全に保護することではなく、モジュールのユーザーからの間違いを避けることだけです。

/*** 2DPoint.h module interface ***/
#ifndef H_2D_POINT
#define H_2D_POINT

/* 2D_POINT_IMPL need to be defined in implementation files before #include */
#ifdef 2D_POINT_IMPL
#define _cst_
#else
#define _cst_ const
#endif

typedef struct 2DPoint
{
    /* public members: read and write for user */
    int x;
    
    /* private members: read only for user */
    _cst_ int y;
} 2DPoint;

2DPoint *new_2dPoint(void);
void delete_2dPoint(2DPoint **pt);
void set_y(2DPoint *pt, int newVal);


/*** 2dPoint.c module implementation ***/
#define 2D_POINT_IMPL
#include "2dPoint.h"
#include <stdlib.h>
#include <string.h>

2DPoint *new_2dPoint(void)
{
    2DPoint *pt = malloc(sizeof(2DPoint));
    pt->x = 42;
    pt->y = 666;

    return pt;
}

void delete_2dPoint(2DPoint **pt)
{
    free(*pt);
    *pt = NULL;
}

void set_y(2DPoint *pt, int newVal)
{
    pt->y = newVal;
}

#endif /* H_2D_POINT */


/*** main.c user's file ***/
#include "2dPoint.h"
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    2DPoint *pt = new_2dPoint();

    pt->x = 10;     /* ok */
    pt->y = 20;     /* Invalid access, y is "private" */    
    set_y(pt, 30);  /* accessor needed */
    printf("pt.x = %d, pt.y = %d\n", pt->x, pt->y);  /* no accessor needed for reading "private" members */

    delete_2dPoint(&pt);
    
    return EXIT_SUCCESS;
}

さて、ここで質問があります。このトリックは C 標準で問題ありませんか? GCC では問題なく動作し、厳密なフラグを使用してもコンパイラは何も文句を言いませんが、これが本当に問題ないことをどのように確認できますか?

4

4 に答える 4

7

これはほぼ確実に未定義の動作です。

禁止されていると宣言されたオブジェクトの書き込み/変更constを行うと、UBになります。さらに、あなたが採用するアプローチは、2つの技術的に異なるタイプとして再宣言struct 2DPointされますが、これも許可されていません。

これは(一般的に未定義の動作として)「確実に機能しない」または「クラッシュする必要がある」という意味ではないことに注意してください。実際、私はそれが機能することは非常に論理的だと思います。なぜなら、ソースをインテリジェントに読むと、その目的が何であり、なぜそれが正しいと見なされるのかを簡単に見つけることができるからです。ただし、コンパイラはインテリジェントではありません。せいぜい、コードが何をするのかについての知識がない有限オートマトンです。それは(多かれ少なかれ)文法の構文的および意味的な規則に従うだけです。

于 2012-12-26T16:55:46.430 に答える
3

これはC20116.2.71に違反します。

6.2.7 1では、異なる翻訳単位の同じ構造の2つの定義が互換性のあるタイプである必要があります。一方に含めることは許可されてconstおらず、もう一方に含めることは許可されていません。

1つのモジュールでは、これらのオブジェクトの1つへの参照があり、メンバーはコンパイラーに対してconstであるように見えます。コンパイラが他のモジュールの関数への呼び出しを書き込む場合、constメンバーからの値をレジスタまたは他のキャッシュに保持するか、関数呼び出しよりもソースコードの後半から部分的または完全に評価された式に保持する場合があります。次に、関数がメンバーを変更して戻るときに、元のモジュールの値は変更されません。さらに悪いことに、変更された値と古い値の組み合わせを使用する場合があります。

これは非常に不適切なプログラミングです。

于 2012-12-26T17:29:11.247 に答える
0

Bjarne Stroustrupの言葉によると、CはOOPをサポートするようには設計されていませんが、OOPを有効にします。つまり、CでOOPプログラムを作成することは可能ですが、それを行うのは非常に困難です。そのため、CでOOPコード作成する必要がある場合は、このアプローチを使用しても問題はないように思われますが、目的により適した言語を使用することをお勧めします。

CでOOPコードを書こうとすると、すでに「常識」を覆さなければならない領域に入っているので、それを正しく使う責任がある限り、このアプローチは問題ありません。また、それが徹底的かつ厳密に文書化されており、コードに関係するすべての人がそれを認識していることを確認する必要があります。

編集ああ、あなたはキャストを使用して回避する必要があるかもしれませんconst。CスタイルのキャストをC++のように使用できるかどうかは思い出せませんconst_cast

于 2012-12-26T16:55:21.810 に答える
-1

別のアプローチを使用できます。2 つの を宣言structします。1 つはプライベート メンバーを持たないユーザー用 (ヘッダー内) で、もう 1 つは実装ユニットで内部使用するためのプライベート メンバーを持ちます。すべてのプライベート メンバーは、パブリック メンバーの後に配置する必要があります。

次のように、常にポインターを に渡し、struct必要に応じて internal-use にキャストします。

/* user code */
struct foo {
    int public;
};

int bar(void) {
    struct foo *foo = new_foo();
    foo->public = 10;
}

/* implementation */
struct foo_internal {
    int public;
    int private;
};

struct foo *new_foo(void) {
    struct foo_internal *foo == malloc(sizeof(*foo));
    foo->public = 1;
    foo->private = 2;
    return (struct foo*)foo;  // to suppress warning
}

C11 では、名前のない構造体フィールドが許可されます(GCC はしばらくサポートします)。そのため、GCC (または C11 準拠のコンパイラ) を使用する場合は、内部構造体を次のように宣言できます。

struct foo_internal {
    struct foo;
    int private;
};

したがって、構造定義の同期を維持するために余分な作業は必要ありません。

于 2012-12-26T19:30:23.107 に答える