C 프로그램에 대한 OO 모범 사례 [닫기]


19

"당신이 정말로 OO 설탕을 원한다면-C ++을 사용하십시오" -나는 이것을 물었을 때 내 친구 중 한 사람으로부터 얻은 즉각적인 반응이었습니다. 나는 여기서 두 가지가 잘못되었다는 것을 안다. 첫 번째 OO는 '설탕'이 아니며 두 번째로 C ++은 C를 흡수하지 않았습니다.

C로 서버를 작성해야하는데 (파이썬에서 사용할 프론트 엔드), 큰 C 프로그램을 관리하는 더 좋은 방법을 모색하고 있습니다.

객체 측면에서 대형 시스템을 모델링하고 객체 상호 작용을 통해 관리, 유지 관리 및 확장 성이 향상됩니다. 그러나이 모델을 객체 (및 그 밖의 모든 것)를 갖지 않는 C로 변환하려고하면 중요한 결정이 필요합니다.

시스템에 필요한 OO 추상화를 제공하기 위해 사용자 정의 라이브러리를 작성합니까? 객체, 캡슐화, 상속, 다형성, 예외, pub / sub (이벤트 / 신호), 네임 스페이스, 내부 검사 등과 같은 것 (예 : GObject 또는 COS ).

또는 기본 C 구문 ( struct및 함수)을 사용하여 모든 객체 클래스 (및 기타 추상화)를 임시 방식으로 근사화합니다. (예를 들어, SO에 대한 이 질문에 대한 답변 중 일부 )

첫 번째 방법은 C에서 전체 모델을 구현할 수있는 구조화 된 방법을 제공하지만 유지 관리해야하는 복잡한 계층도 추가합니다. (처음에는 객체를 사용하여 복잡성을 줄이고 싶었던 복잡성을 기억하십시오).

나는 두 번째 접근법에 대해 모른다. 그리고 그것이 당신이 요구할 수있는 모든 추상화를 근사화하는 데 얼마나 효과적인지 모른다.

그래서 간단한 질문은 다음과 같습니다. C에서 객체 지향 디자인을 구현하는 가장 좋은 방법은 무엇입니까? 어떻게해야하는지 묻지 않습니다. 이것 과이 질문은 그것에 대해 이야기 하며 이것에 관한 도 있습니다. 내가 더 관심이있는 것은 이것을 움직일 때 나타나는 실제 문제를 해결하는 현실적인 조언 / 예입니다.

참고 : C를 C ++에 유리하게 사용해서는 안되는 이유를 조언하지 마십시오. 우리는 그 단계를 지났습니다.


3
C ++ 서버를 작성하여 외부 인터페이스이며 extern "C"파이썬에서 사용할 수 있습니다. 수동으로 수행하거나 SWIG가 도움을 줄 수 있습니다 . 따라서 파이썬 프론트 엔드에 대한 욕구는 C ++을 사용하지 않는 이유가 아닙니다. C와 함께 머물고 싶은 타당한 이유가 없다고 말하는 것은 아닙니다.
Jan Hudec

1
이 질문은 설명이 필요합니다. 현재, 단락 4와 5는 기본적으로 어떤 접근 방식을 취해야하는지 묻지 만, "어떻게해야하는지 묻지 않고"모범 사례를 원한다고 말합니다. C에서 어떻게해야하는지 찾고 있지 않다면 일반적으로 OOP와 관련된 "모범 사례"목록을 요청하십니까? 그렇다면, 그렇게 말하되, 주관적인 것으로 인해 질문이 종결 될 수 있다는 점에 유의하십시오 .
Caleb

:) 실제 예제 (코드 또는 기타)가 수행 된 곳과 수행 할 때 발생하는 문제를 묻고 있습니다.
treecoder

4
요구 사항이 혼란스러워 보입니다. 당신은 내가 볼 수있는 이유없이 객체 지향을 사용해야한다고 주장합니다 (일부 언어에서는 프로그램을보다 유지 보수 가능하게하지만 C에서는 사용하지 못하게하는 방법입니다) .C는 사용을 주장합니다. . 또한 언어 지원으로 큰 혜택을받습니다. 실제로 OO를 원한다면 언어를 선택하는 동안이를 고려해야합니다. C로 대규모 소프트웨어 시스템을 만드는 방법에 대한 질문은 훨씬 더 합리적입니다.
David Thornley

"객체 지향 모델링 및 디자인"을 살펴볼 수 있습니다. (Rumbaugh et al.) : OO 디자인을 C와 같은 언어로 매핑하는 방법에 대한 섹션이 있습니다.
Giorgio

답변:


16

내 대답 에서 C로 복잡한 프로젝트어떻게 구성해야합니까? (OO가 아니라 C에서 복잡성을 관리하는 방법) :

핵심은 모듈성입니다. 이것은 설계, 구현, 컴파일 및 유지 관리가 더 쉽습니다.

  • OO 앱의 클래스와 같이 앱에서 모듈을 식별하십시오.
  • 각 모듈에 대한 별도의 인터페이스 및 구현으로 다른 모듈에 필요한 것만 인터페이스에 넣습니다. C에는 네임 스페이스가 없으므로 인터페이스의 모든 것을 고유하게 만들어야합니다 (예 : 접두사 사용).
  • 구현에서 전역 변수를 숨기고 읽기 / 쓰기에 접근 자 함수를 사용하십시오.
  • 상속의 관점에서가 아니라 구성의 관점에서 생각하십시오. 일반적으로 C에서 C ++를 모방하려고 시도하지 마십시오. 읽기 및 유지 관리가 매우 어려울 수 있습니다.

내 대답에서 OO C 공용 및 개인 기능에 대한 일반적인 명명 규칙은 무엇입니까 (나는 이것이 최선의 방법이라고 생각합니다).

내가 사용하는 규칙은 다음과 같습니다.

  • 퍼블릭 함수 (헤더 파일) :

    struct Classname;
    Classname_functionname(struct Classname * me, other args...);
  • 전용 함수 (구현 파일에서 정적)

    static functionname(struct Classname * me, other args...)

또한 많은 UML 도구는 UML 다이어그램에서 C 코드를 생성 할 수 있습니다. 오픈 소스는 Topcased 입니다.



1
좋은 대답입니다. 모듈성을 목표로합니다. OO는 그것을 제공해야하지만 1) 실제로는 OO 스파게티로 끝나기에는 너무 일반적이며 2) 유일한 방법은 아닙니다. 실생활의 일부 예제는 Linux 커널 (C 스타일 모듈) 및 glib (C 스타일 OO)를 사용하는 프로젝트를 살펴보십시오. 나는 두 가지 스타일로 작업 할 수있는 기회를 얻었으며 IMO C 스타일 모듈화가 승리합니다.
Joh

그리고 왜 구성이 상속보다 더 나은 접근 방식입니까? 이론적 근거와 참고 문헌을 환영합니다. 아니면 C 프로그램 만 언급 했습니까?
Aleksandr Blekh

1
@ AleksandrBlekh-예 C 만 언급하고 있습니다.
mouviciel

16

이 토론에서 OO와 C ++를 차별화해야한다고 생각합니다.

C에서는 객체를 구현할 수 있으며 함수 포인터로 구조를 만드는 것이 매우 쉽습니다. 그것이 당신의 "두 번째 접근"이고, 나는 그것을 갈 것입니다. 또 다른 옵션은 구조체에서 함수 포인터를 사용하지 않고 직접 컨텍스트에서 "컨텍스트"포인터로 데이터 구조체를 함수에 전달하는 것입니다. IMHO는 더 읽기 쉽고 추적하기 쉽고 구조체를 변경하지 않고 함수를 추가 할 수 있기 때문에 더 좋습니다 (데이터가 추가되지 않으면 상속이 용이함). 사실 C ++이 일반적으로 this포인터를 구현하는 방법 입니다.

상속 및 추상화 지원이 기본적으로 제공되지 않기 때문에 다형성이 더 복잡해 지므로, 부모 클래스를 자식 클래스에 포함 시키거나 많은 복사 붙여 넣기를 수행해야합니다. 단순한. 대기중인 엄청난 양의 버그.

가상 함수는 필요에 따라 다른 함수를 가리키는 함수 포인터를 통해 쉽게 달성 할 수 있습니다. 다시 수동으로 할 때 버그가 발생하기 쉽고 포인터를 올바르게 초기화하는 데 많은 지루한 작업이 있습니다.

네임 스페이스, 예외, 템플릿 등에 관해서는-C로 제한되면-그냥 포기해야한다고 생각합니다. 나는 C로 OO를 작성하는 데 노력했지만, 선택의 여지가 있다면 그렇게하지 않을 것입니다. 나머지 C 모듈을 마지막에 놓습니다.).

C ++을 사용할 수 있다면 C ++을 사용하십시오. 할 이유가 없습니다.


실제로 구조체에서 상속하고 데이터를 추가 할 수 있습니다. 자식 구조체의 첫 번째 항목을 부모 구조체 형식의 변수로 선언하면됩니다. 그런 다음 필요에 따라 전송하십시오.
mouviciel

1
@mouviciel-예. 나는 말했다. " ... 따라서 자녀 클래스에 부모 구조체를 포함 시키거나 ... "
littleadv

5
상속을 시도 할 이유가 없습니다. 코드 재사용을위한 수단으로 시작하는 것은 잘못된 아이디어입니다. 객체 구성이 더 쉽고 좋습니다.
KaptajnKold

@KaptajnKold-동의합니다.
littleadv

8

C에서 객체 방향을 만드는 방법의 기본 사항은 다음과 같습니다.

1. 객체와 캡슐화 만들기

일반적으로-다음과 같은 객체를 만듭니다.

object_instance = create_object_typex(parameter);

여기에서 두 가지 방법 중 하나로 방법을 정의 할 수 있습니다.

object_type_method_function(object_instance,parameter1)
OR
object_instance->method_function(object_instance_private_data,parameter1)

대부분의 경우 object_instance (or object_instance_private_data)반환되는 유형 void *.은 응용 프로그램입니다. 응용 프로그램은이 멤버의 개별 멤버 또는 함수를 참조 할 수 없습니다.

이 외에도 각 메소드는 후속 메소드에 이러한 object_instance를 사용합니다.

2. 다형성

많은 함수와 함수 포인터를 사용하여 런타임에 특정 기능을 재정의 할 수 있습니다.

예를 들어, 모든 object_methods는 개인 메소드뿐만 아니라 public으로 확장 될 수있는 함수 포인터로 정의됩니다.

var_args printf에 정의 된 가변 개수의 인수와 매우 유사한 방식으로 함수 오버로드를 제한된 의미로 적용 할 수도 있습니다 . 예, 이것은 C ++에서 유연하지는 않지만 가장 가까운 방법입니다.

3. 상속 정의

상속을 정의하는 것은 약간 까다 롭지 만 구조를 사용하여 다음을 수행 할 수 있습니다.

typedef struct { 
     int age,
     int sex,
} person; 

typedef struct { 
     person p,
     enum specialty s;
} doctor;

typedef struct { 
     person p,
     enum subject s;
} engineer;

// use it like
engineer e1 = create_engineer(); 
get_person_age( (person *)e1); 

여기에서 doctorand engineer는 person에서 파생되며 더 높은 수준으로 타입 캐스트 할 수 person있습니다.

이에 대한 가장 좋은 예는 GObject 및 파생 개체에서 사용됩니다.

4. 가상 클래스 만들기 모든 브라우저에서 jpeg 디코딩에 사용하는 libjpeg라는 라이브러리에서 실제 예제를 인용하고 있습니다. 응용 프로그램이 구체적인 인스턴스를 생성하고 다시 공급할 수있는 error_manager라는 가상 클래스를 만듭니다.

struct djpeg_dest_struct {
  /* start_output is called after jpeg_start_decompress finishes.
   * The color map will be ready at this time, if one is needed.
   */
  JMETHOD(void, start_output, (j_decompress_ptr cinfo,
                               djpeg_dest_ptr dinfo));
  /* Emit the specified number of pixel rows from the buffer. */
  JMETHOD(void, put_pixel_rows, (j_decompress_ptr cinfo,
                                 djpeg_dest_ptr dinfo,
                                 JDIMENSION rows_supplied));
  /* Finish up at the end of the image. */
  JMETHOD(void, finish_output, (j_decompress_ptr cinfo,
                                djpeg_dest_ptr dinfo));

  /* Target file spec; filled in by djpeg.c after object is created. */
  FILE * output_file;

  /* Output pixel-row buffer.  Created by module init or start_output.
   * Width is cinfo->output_width * cinfo->output_components;
   * height is buffer_height.
   */
  JSAMPARRAY buffer;
  JDIMENSION buffer_height;
};

여기서 JMETHOD는 매크로를 통해 함수 포인터로 확장되며 각각 올바른 메소드로로드되어야합니다.


나는 너무 많은 개인적인 설명없이 많은 것을 말하려고 노력했습니다. 그러나 나는 사람들이 자신의 것을 시도 할 수 있기를 바랍니다. 그러나 내 의도는 사물이 어떻게 매핑되는지 보여주는 것입니다.

또한 이것이 C ++에 해당하는 속성 이 아니라는 많은 주장이 있습니다 . 나는 C의 OO가 그 정의에 엄격하지 않다는 것을 안다. 그러나 그런 식으로 일하면 몇 가지 핵심 원칙을 이해할 것입니다.

중요한 것은 OO가 C ++ 및 JAVA만큼 엄격하지 않다는 것입니다. OO 사고를 염두에두고 코드를 구조적으로 구성하고 그렇게 운영 할 수 있다는 것입니다.

나는 사람들에게 libjpeg 의 실제 디자인 과 다음 자료 를 볼 것을 강력히 권합니다.

ㅏ. C에서의 객체 지향 프로그래밍
b. 사람들이 아이디어를 교환 할 수있는 좋은 장소
입니다. c. 그리고 여기에 전체 책이 있습니다


3

객체 지향은 세 가지로 요약됩니다.

1) 자율적 인 수업으로 모듈 형 프로그램 디자인.

2) 개인 캡슐화를 통한 데이터 보호.

3) 상속 / 다형성 및 생성자 / 소멸자, 템플릿 등과 같은 다양한 유용한 구문

1은 지금까지 가장 중요하며 언어와 무관하며 프로그램 디자인에 관한 것입니다. C에서는 하나의 .h 파일과 하나의 .c 파일로 구성된 자율적 인 "코드 모듈"을 작성하여이를 수행합니다. 이것을 OO 클래스와 동등한 것으로 간주하십시오. 상식, UML 또는 C ++ 프로그램에 사용중인 OO 디자인 방법을 통해이 모듈에 배치 할 항목을 결정할 수 있습니다.

또한 개인 데이터에 대한 의도적 인 액세스를 다시 보호 할뿐만 아니라 "네임 스페이스 클러 터"와 같은 의도하지 않은 액세스를 방지하는 것도 중요합니다. C ++는 C보다 더 우아한 방식으로이 작업을 수행하지만 static 키워드를 사용하여 C에서 여전히 달성 할 수 있습니다. C ++ 클래스에서 private로 선언 한 모든 변수는 C에서 static으로 선언하고 파일 범위에 배치해야합니다. 자체 코드 모듈 (클래스) 내에서만 액세스 할 수 있습니다. C ++에서와 마찬가지로 "세터 / 게터"를 작성할 수 있습니다.

3은 도움이되지만 필요하지는 않습니다. 상속이나 생성자 / 소멸자없이 OO 프로그램을 작성할 수 있습니다. 이러한 것들이 있으면 좋으며, 프로그램을 더욱 우아하고 안전하게 만들 수 있습니다 (부주의하게 사용하는 경우 반대). 그러나 그들은 필요하지 않습니다. C는 이러한 유용한 기능 중 어느 것도 지원하지 않기 때문에 기능없이 수행하면됩니다. 생성자를 초기화 / 파괴 함수로 대체 할 수 있습니다.

상속은 다양한 구조체 트릭을 통해 수행 할 수 있지만, 아무런 이득없이 프로그램을 더 복잡하게 만들 수 있기 때문에 조언하지 않을 것입니다 (일반적으로 상속은 C뿐만 아니라 모든 언어로 신중하게 적용되어야 함).

마지막으로 모든 OO 트릭 C에서 수행 할 수 있습니다. Axel-Tobias Schreiner의 저서 "ANSI C를 사용한 객체 지향 프로그래밍"이 90 년대 초반에이를 증명합니다. 그러나 나는 그 책을 다른 사람에게 추천하지 않을 것입니다 : 그것은 소란할만한 가치가없는 C 프로그램에 불쾌하고 이상한 복잡성을 더합니다. ( 내 경고에도 불구하고 여전히 관심있는 사람들을 위해이 책은 무료로 제공 됩니다 .)

따라서 위의 1)과 2)를 구현하고 나머지는 건너 뛰는 것이 좋습니다. 20 년 넘게 성공한 것으로 입증 된 C 프로그램을 작성하는 방법입니다.


2

다양한 Objective-C 런타임의 경험을 빌려 C로 동적 다형성 OO 기능을 작성하는 것은 그리 어렵지 않습니다 (반면에 빠르고 사용하기 쉬운 것은 25 년이 지난 후에도 계속되는 것처럼 보입니다). 그러나 언어 구문을 확장하지 않고 Objective-C 스타일 객체 기능을 구현하면 결국 코드가 지저분합니다.

  • 모든 클래스는 수퍼 클래스를 선언하는 구조, 준수하는 인터페이스, 구현하는 메시지 ( "선택기", 메시지 이름, "구현", 동작을 제공하는 함수) 로의 클래스 및 클래스의 인스턴스로 정의됩니다. 가변 레이아웃.
  • 모든 인스턴스는 클래스에 대한 포인터와 인스턴스 변수를 포함하는 구조로 정의됩니다.
  • 메시지 전송은 다음과 같은 기능을 사용하여 구현됩니다 (특별한 경우를 주거나 받음) objc_msgSend(object, selector, …). 객체가 어떤 클래스의 인스턴스인지 알면 선택기와 일치하는 구현을 찾아 올바른 기능을 실행할 수 있습니다.

이는 여러 개발자가 서로의 클래스를 사용하고 확장 할 수 있도록 설계된 범용 OO 라이브러리의 모든 부분이므로 자신의 프로젝트에 지나치게 무리를 줄 수 있습니다. 나는 종종 C 프로젝트를 구조와 함수를 사용하여 "정적"클래스 지향 프로젝트로 설계했습니다.-각 클래스는 ivar 레이아웃을 지정하는 C 구조의 정의입니다.-각 인스턴스는 해당 구조의 인스턴스 일뿐입니다. "메시지", 메소드와 유사한 함수 MyClass_doSomething(struct MyClass *object, …)가 정의되어 있습니다. 이것은 ObjC 접근 방식보다 코드에서 더 명확하지만 유연성은 떨어집니다.

균형을 잡는 위치는 자신의 프로젝트에 달려 있습니다. 다른 프로그래머가 C 인터페이스를 사용하지 않는 것처럼 들리므로 선택은 내부 환경 설정에 달려 있습니다. 물론 objc 런타임 라이브러리와 같은 것을 원한다면 서비스를 제공하는 크로스 플랫폼 objc 런타임 라이브러리가 있습니다.


1

GObject는 실제로 복잡성을 숨기지 않으며 자체 복잡성을 도입합니다. 신호 또는 인터페이스 기계와 같은 고급 GObject 항목이 필요하지 않으면 임시 객체가 GObject보다 쉽다고 말할 수 있습니다.

일부 OO 구문으로 C 구문을 확장하는 전처리 기가 제공되므로 COS와 약간 다릅니다. GObject와 유사한 전처리 기인 G Object Builder가 있습니다.

Vala 프로그래밍 언어는 C로 컴파일되는 평범한 고급 언어이며 일반 C 코드에서 Vala 라이브러리를 사용할 수있는 방식으로 시도 할 수도 있습니다. GObject, 자체 객체 프레임 워크 또는 ad-hoc 방식 (제한된 기능)을 사용할 수 있습니다.


1

먼저, 이것이 숙제이거나 대상 장치에 C ++ 컴파일러가 없으면 C를 사용해야한다고 생각하지 않는 한 C ++의 C 연결을 사용하여 C 인터페이스를 쉽게 제공 할 수 있습니다.

둘째, 나는 당신이 다형성과 예외에 얼마나 의존 할 것인지, 프레임 워크가 제공 할 수있는 다른 기능들에 대해 살펴볼 것입니다. 디자인은 그들이 총알을 물고 프레임 워크를 사용하므로 기능을 직접 구현할 필요가 없습니다.

실제로 결정을 내릴 디자인이 없다면 스파이크를 수행하고 코드가 알려주는 내용을 확인하십시오.

마지막으로 하나 또는 다른 선택 일 필요는 없습니다 (시작부터 프레임 워크를 사용하는 경우 아마도 프레임 워크를 고수 할 수는 있지만) 간단한 부분에 대한 간단한 구조체로 시작하고 라이브러리에만 추가 할 수 있어야합니다 필요에 따라.

편집 : 따라서 관리 권한에 의한 결정은 첫 번째 시점을 무시하게합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.