2016-08-01 9 views
3

다른 플랫폼에서 코드를 실행할 수 있도록 추상화 레이어를 작성하려고합니다. 제가 궁극적으로 높은 수준의 코드에 사용할 두 개의 클래스를 예를 들어 보겠습니다 :디자인 패턴 : C++ 추상화 레이어

class Thread 
{ 
public: 
    Thread(); 
    virtual ~Thread(); 

    void start(); 
    void stop(); 

    virtual void callback() = 0; 
}; 

class Display 
{ 
public: 
    static void drawText(const char* text); 
}; 

내 문제는 : 내가 구현에 낮은 수준의 코드를 칠 수 있도록하는 데 사용할 수있는 어떤 디자인 패턴? 여기 내 thoughs하고 나는 생각하지 않는다 왜 좋은 솔루션입니다 : 이론적으로

  1. lowLevel/platformA/thread.cpp에 앉아 highLevel/thread.h 위의 정의 앉아 및 플랫폼 관련 구현을 가지고 아무런 문제가 없습니다. 이것은 링크 시간에 해결되는 오버 헤드가 낮은 솔루션입니다. 유일한 문제는 하위 수준 구현에서 멤버 변수 또는 멤버 함수를 추가 할 수 없다는 점입니다. 이것은 특정 일을 구현하는 것을 불가능하게 만듭니다.

  2. 방법이 밖으로 정의 (기본적으로 Pimpl - 관용구)이를 추가하는 것입니다 :

    class Thread 
    { 
        // ... 
    private: 
        void* impl_data; 
    } 
    

    이제 낮은 수준의 코드는 무효 포인터에 저장된 자신의 구조체 또는 객체의 수 있습니다. 여기서 문제는 그 추악한 프로그램을 읽고 고통 스럽다는 것입니다.

  3. 나는 class Thread을 순수 가상으로 만들 수 있으며 저수준 기능을 상속하여 구현할 수 있습니다. 이 정도로 깔끔한 것

    // thread.h, below the pure virtual class definition 
    extern "C" void* makeNewThread(); 
    
    // in lowlevel/platformA/thread.h 
    class ThreadImpl: public Thread 
    { ... }; 
    
    // in lowLevel/platformA/thread.cpp 
    extern "C" void* makeNewThread() { return new ThreadImpl(); } 
    

    하지만 정적 클래스 실패 : 높은 수준의 코드는 다음과 같은 공장 함수를 호출하여 낮은 수준의 구현에 액세스 할 수 있습니다. 내 추상화 계층은 하드웨어 및 IO 작업에 사용될 것이고 하나의 Display 클래스에 대한 포인터를 가지고 다니는 대신 Display::drawText(...)을 가질 수 있기를 정말로 바랄 것입니다.

  4. 다른 옵션은 extern "C" handle_t createThread()과 같이 링크 타임에 해결할 수있는 C 스타일 기능 만 사용하는 것입니다. 이것은 디스플레이와 같이 한 번만있는 저수준 하드웨어에 액세스하기 쉽고 간단합니다. 그러나 여러 번 (잠금, 스레드, 메모리 관리) 할 수있는 모든 것에 대해 나는 핸들이 숨겨진 높은 수준의 래퍼 클래스를 가지고 있거나 못생긴 코드를 처리해야한다. 어느 쪽이든 나는 높은 수준과 낮은 수준 측면에서 각각의 기능과 핸들을 연결해야하는 오버 헤드가 있습니다.

  5. 마지막으로 생각한 것은 하이브리드 구조입니다. 순수 C 스타일 extern "C"은 단 한 번 밖에없는 저수준의 기능을합니다. 여러 번있을 수있는 물건에 대한 공장 기능 (3. 참조). 그러나 뭔가 잡종이 일관성없고 읽을 수없는 코드로 이어질까봐 두렵습니다.

내 요구 사항에 맞는 패턴을 설계하는 데 도움이 될만한 정보를 주시면 감사하겠습니다.

+4

아직 전체 질문을 읽어 보지 않았. 그러나 C++은 C++ 11 이후 스레드에 대한 추상화 계층을 가지고 있습니다. – StoryTeller

+0

나는 스레딩 물건을 먹으면서 FreeRTOS가있는 임베디드 타겟에서 작업하고 있습니다. 또 다른 플랫폼은 높은 수준의 코드를 쉽게 개발할 수있는 컴퓨터 기반 에뮬레이션입니다. C++ 11이이 시나리오를 지원한다고 생각하지 않습니다. – LoveDaOOP

답변

0

Thread 클래스에 대한 가치 의미론을 원할하고 간접 참조를 추가하여 어디서 이식성있게 만들 수 있는지 궁금해합니다. 그래서 당신은 pimpl 관용구와 일부 조건부 컴파일을 사용합니다. 당신이 할 수있는 빌드 도구의 복잡성을 원하고, 당신이 가능한 한 포함 된 자체로 모든 낮은 수준의 코드를 유지하려는 경우, 당신은 다음을 수행 장소에 따라
: 당신에

높은 수준의 헤더 Thread.hpp을, 당신 정의 :

Thread_PlatformA.cpp

#ifdef PLATFORM_A 

#include <Thread.hpp> 

Thread::Thread() 
{ 
    // Platform A specific code goes here, initialize the pimpl; 
} 

Thread::~Thread() 
{ 
    // Platform A specific code goes here, release the pimpl; 
} 

#endif 
:

class Thread 
{ 
    class Impl: 
    Impl *pimpl; // or better yet, some smart pointer 
public: 
    Thread(); 
    ~Thread(); 
    // Other stuff; 
}; 

보다가, 스레드 소스 디렉토리에, 당신은이 방식에 따라 파일을 정의

빌딩 Thread.o은 스레드 디렉토리에있는 모든 파일을 Thread_*.cpp 개로 취하고 빌드 시스템에 컴파일러에 올바른 -D 옵션이 표시되도록하는 간단한 문제가됩니다. (단지 스레드을 고수) 다음과 같은이 상황 디자인 것이 어떨지 궁금 해요

+0

예, 본 적이 있습니다. 전에 Pimpl 관용구. 기본적으로 내 첫 게시물 2입니다. 나는 진지하게 이것을 고려했지만 코드가 정말 추악 해지는 것을 알았다. – LoveDaOOP

+0

@LoveDaOOP, 만약 당신이'void * '를 사용했다면. 타입 시스템을 우회하지 않으면, 그렇게 많이하지 않는 것. – StoryTeller

+0

이것이 가장 깔끔한 해결책 인 것 같아서 지금이 대답으로 받아들입니다. 내 프로젝트에서 나는 궁극적으로 C- 스타일의 단순한 인터페이스를 사용했다. 상위 레벨에서는 모든 핸들을 숨기는 래퍼 클래스가 있습니다. 핸들을'typedef void * threadHandle_t'로 정의함으로써 저의 저수준 코드가 적절하다고 생각되는대로 핸들을 사용할 수 있습니다. 종종 핸들은 하위 수준 코드에서 객체에 대한 포인터로 남용됩니다. 그렇게하면 저수준 쪽에서 각각의 객체와 핸들을 연결하기 위해 테이블이나 맵을 사용할 필요가 없습니다. 모두에게 감사드립니다! – LoveDaOOP

1

코드는 한 번에 단일 콘크리트 플랫폼 용으로 만 컴파일되므로 플랫폼에 독립적 인 기본 클래스가 필요하지 않습니다.

포함 경로를 예 : -Iinclude/generic -Iinclude/platform으로 설정하고 의 include 디렉토리에 각각 별도의 Thread 클래스가 있습니다.

기본적으로 실행되는 &으로 컴파일 된 플랫폼 독립적 테스트를 작성할 수 있으며, 플랫폼에 독립적 인 다른 구현이 동일한 인터페이스와 의미에 부합하는지 확인할 수 있습니다.

추신. StoryTeller가 말했듯이, 쓰레드는 이미 휴대용 std::thread이므로 나쁜 예입니다. 당신이 실제로 추상화 할 필요가있는 다른 플랫폼 특정 세부 사항이 있다고 가정합니다.

PPS. generic (플랫폼에 구애받지 않음) 코드와 플랫폼 특정 코드 사이의 올바른 구분을 알아야합니다. 어디로 가야할지 결정할 수있는 마법의 탄환이 없습니다. 재사용/복제, 간단한 매개 변수화와 고도의 매개 변수화 간의 일련의 절충점 코드 등

+1

분명히 별도의'Thread' 클래스가 아니라'Thread' 클래스의 분리 된 * 구현물입니다. 그리고 나는 소스 디렉토리이므로 include 경로를 따르지 않습니다 :) – StoryTeller

+0

서로 다른 디렉토리에 두 개의 서로 다른 클래스 (둘 다'Thread'라고도 함)를 작성하면 각기 다른 동일한 공용 인터페이스가 노출되지만 다르게 구현됩니다. _only one은 주어진 번역 단위와 전체 프로그램에 포함될 수 있습니다 ... 그들은 다른 클래스입니까? 동일한 클래스의 다른 구현? 유용한 차이가없는 구분입니다. – Useless

+0

ODR에서는 시스템에 이러한 클래스가 하나만 있다고합니다. 그리고 그것은 상위 레벨 헤더에 정의되어 있습니다. 구현은 다양 할 수 있습니다. 그리고 의미론에 대한 정확한 이해는 OP가 자신이해야 할 일을 파악하는 데 도움이 될 수 있습니다. – StoryTeller

0

:

// Your generic include level: 
// thread.h 
class Thread : public 
#ifdef PLATFORM_A 
    PlatformAThread 
#elif PLATFORM_B 
    PlatformBThread 
// any more stuff you need in here 
#endif 
{ 
    Thread(); 
    virtual ~Thread(); 

    void start(); 
    void stop(); 

    virtual void callback() = 0; 
} ; 

하는 것은 구현에 대해 아무것도, 단지 인터페이스를 포함하지 않는를

그런 다음 당신은 :

// platformA directory 
class PlatformAThread { ... }; 

이 자동으로 자동으로 g 당신이 당신의 "일반"Thread 객체를 만들 때 발생합니다 또한 내부적으로 자동으로 설정하고 플랫폼 관련 작업을 수행 할 수있는 플랫폼 종속 클래스인데 확실히 PlatformAThread 클래스는 필요한 공통점이있는 일반 Base 클래스에서 파생 될 수 있습니다.

플랫폼 별 디렉토리를 자동으로 인식하도록 빌드 시스템을 설정해야합니다. 또한

, 나는 클래스 상속의 계층 구조를 만들 수있는 경향이 그주의하시기 바랍니다, 어떤 사람들이 무리를 줄 수 : https://en.wikipedia.org/wiki/Composition_over_inheritance

+0

좋은 아이디어입니다. 그러나 상위 레벨 코드는 실행될 플랫폼을 알고 있다고 가정합니다. 나는 stringyfication과 함께 매크로를 사용하여 메이크 파일을 통해 올바른 클래스 이름을 제공 할 수있다. 이렇게 : 'class Thread : public TREAD_IMPL' 그런 다음 빌드 설정에서 올바른 전 처리기 정의를 추가 할 수 있습니다. buts도 일종의 지저분합니다. – LoveDaOOP