문제

이 질문에는 이미 답변이 있습니다.

배경 설명:

그만큼 PIMPL 관용구 (구현 포인터)는 공용 클래스가 해당 공용 클래스가 속한 라이브러리 외부에서 볼 수 없는 구조나 클래스를 래핑하는 구현 숨기기 기술입니다.

이렇게 하면 라이브러리 사용자에게 내부 구현 세부 정보와 데이터가 숨겨집니다.

이 관용구를 구현할 때 공용 클래스 메서드 구현이 라이브러리로 컴파일되고 사용자가 헤더 파일만 가지게 되므로 공용 클래스가 아닌 pimpl 클래스에 공용 메서드를 배치하는 이유는 무엇입니까?

설명을 위해 이 코드는 Purr() impl 클래스에 구현하고 이를 래핑합니다.

공개 클래스에 Purr을 직접 구현해 보는 것은 어떨까요?

// header file:
class Cat {
    private:
        class CatImpl;  // Not defined here
        CatImpl *cat_;  // Handle

    public:
        Cat();            // Constructor
        ~Cat();           // Destructor
        // Other operations...
        Purr();
};


// CPP file:
#include "cat.h"

class Cat::CatImpl {
    Purr();
...     // The actual implementation can be anything
};

Cat::Cat() {
    cat_ = new CatImpl;
}

Cat::~Cat() {
    delete cat_;
}

Cat::Purr(){ cat_->Purr(); }
CatImpl::Purr(){
   printf("purrrrrr");
}
도움이 되었습니까?

해결책

  • 당신이 원하기 때문에 Purr() 개인 멤버를 사용할 수 있도록 CatImpl. Cat::Purr() 없이는 그러한 액세스가 허용되지 않습니다. friend 선언.
  • 그러면 책임을 혼합하지 않기 때문입니다.한 클래스는 구현하고, 한 클래스는 전달합니다.

다른 팁

나는 대부분의 사람들이 이것을 Handle Body 관용구라고 부른다고 생각합니다.James Coplien의 저서 Advanced C++ 프로그래밍 스타일 및 관용구(아마존 링크).그것은 또한 다음과 같이 알려져 있습니다. 체셔 고양이 점점 사라져 미소만 남게 되는 루이스 캐럴의 성격 때문이다.

예제 코드는 두 세트의 소스 파일에 배포되어야 합니다.그러면 Cat.h만이 제품과 함께 제공되는 파일입니다.

CatImpl.h는 Cat.cpp에 포함되어 있으며 CatImpl.cpp에는 CatImpl::Purr()에 대한 구현이 포함되어 있습니다.이는 귀하의 제품을 사용하는 대중에게 표시되지 않습니다.

기본적으로 아이디어는 엿보는 눈으로부터 구현을 최대한 숨기는 것입니다.이는 고객의 코드가 컴파일되고 링크되는 API를 통해 액세스되는 일련의 라이브러리로 제공되는 상용 제품이 있는 경우에 가장 유용합니다.

우리는 2000년에 IONA Orbix 3.3 제품을 다시 작성하여 이를 수행했습니다.

다른 사람들이 언급했듯이 그의 기술을 사용하면 구현과 개체 인터페이스가 완전히 분리됩니다.그러면 Purr()의 구현을 변경하려는 경우 Cat을 사용하는 모든 것을 다시 컴파일할 필요가 없습니다.

이 기술은 다음과 같은 방법론에서 사용됩니다. 계약에 의한 디자인.

가치 있는 점은 인터페이스와 구현을 분리하는 것입니다.이는 일반적으로 소규모 프로젝트에서는 그다지 중요하지 않습니다.그러나 대규모 프로젝트와 라이브러리에서는 빌드 시간을 크게 줄이는 데 사용할 수 있습니다.

구현을 고려하십시오 Cat 많은 헤더를 포함할 수 있고 자체적으로 컴파일하는 데 시간이 걸리는 템플릿 메타 프로그래밍이 포함될 수 있습니다.단지 사용하려는 사용자가 왜 Cat 그걸 다 포함해야 해?따라서 필요한 모든 파일은 pimpl 관용어를 사용하여 숨겨집니다(따라서 다음의 전방 선언은 CatImpl), 인터페이스를 사용해도 사용자가 이를 포함하도록 강요하지는 않습니다.

저는 템플릿에 구현된 비선형 최적화("많은 불쾌한 수학" 읽기)용 라이브러리를 개발 중이므로 대부분의 코드가 헤더에 있습니다.(괜찮은 멀티 코어 CPU에서) 컴파일하는 데 약 5분이 소요되며, 빈 공간에서 헤더를 구문 분석하는 데만 소요됩니다. .cpp 약 1분 정도 소요됩니다.따라서 라이브러리를 사용하는 사람은 코드를 컴파일할 때마다 몇 분 정도 기다려야 하며, 이는 개발을 상당히 어렵게 만듭니다. 지루한.그러나 구현과 헤더를 숨김으로써 즉시 컴파일되는 간단한 인터페이스 파일만 포함하게 됩니다.

구현이 다른 회사에 의해 복사되지 않도록 보호하는 것과 반드시 ​​관련이 있는 것은 아닙니다. 멤버 변수의 정의에서 알고리즘의 내부 작동을 추측할 수 없는 한 그런 일이 발생하지 않을 것입니다(그렇다면 아마도 그다지 복잡하지도 않고 애초에 보호할 가치도 없을 것입니다).

클래스에서 pimpl 관용구를 사용하는 경우 공용 클래스에서 헤더 파일을 변경하지 않아도 됩니다.

이를 통해 외부 클래스의 헤더 파일을 수정하지 않고도 pimpl 클래스에 메서드를 추가/제거할 수 있습니다.pimpl에도 #include를 추가/제거할 수도 있습니다.

외부 클래스의 헤더 파일을 변경할 때 해당 클래스를 #include하는 모든 항목을 다시 컴파일해야 합니다(그리고 그 중 하나라도 헤더 파일인 경우 해당 클래스를 #include하는 모든 항목을 다시 컴파일해야 하는 등).

일반적으로 Owner 클래스(이 경우 Cat)의 헤더에서 Pimpl 클래스에 대한 유일한 참조는 종속성을 크게 줄일 수 있기 때문에 여기에서 수행한 것처럼 전방 선언입니다.

예를 들어, Pimpl 클래스에 ComplicatedClass가 멤버로 있는 경우(단지 포인터나 참조가 아님) 사용하기 전에 ComplicatedClass를 완전히 정의해야 합니다.실제로 이는 "ComplicatedClass.h"를 포함한다는 의미입니다(ComplicatedClass가 의존하는 모든 것을 간접적으로 포함함).이로 인해 단일 헤더 채우기로 인해 많은 내용이 포함될 수 있으며 이는 종속성 및 컴파일 시간을 관리하는 데 좋지 않습니다.

pimpl idion을 사용하는 경우 소유자 유형(여기에서는 Cat)의 공개 인터페이스에 사용되는 항목만 #include하면 됩니다.이는 도서관을 사용하는 사람들에게 더 나은 환경을 제공하며 실수로 또는 허용하지 않는 작업을 수행하려고 하여 도서관의 일부 내부 부분에 의존하는 사람들에 대해 걱정할 필요가 없음을 의미합니다. #define 파일을 포함하기 전에 비공개 공개.

간단한 클래스라면 일반적으로 Pimpl을 사용할 이유가 없지만 유형이 꽤 큰 경우에는 큰 도움이 될 수 있습니다(특히 긴 빌드 시간을 피하는 데).

글쎄, 나는 그것을 사용하지 않을 것이다.더 나은 대안이 있습니다.

foo.h:

class Foo {
public:
    virtual ~Foo() { }
    virtual void someMethod() = 0;

    // This "replaces" the constructor
    static Foo *create();
}

foo.cpp:

namespace {
    class FooImpl: virtual public Foo {

    public:
        void someMethod() { 
            //....
        }     
    };
}

Foo *Foo::create() {
    return new FooImpl;
}

이 패턴에 이름이 있나요?

Python 및 Java 프로그래머로서 저는 pImpl 관용구보다 이것을 훨씬 더 좋아합니다.

우리는 멤버 함수 실행 전후에 사전, 사후 및 오류 측면이 호출되는 측면 지향 프로그래밍을 에뮬레이트하기 위해 PIMPL 관용구를 사용합니다.

struct Omg{
   void purr(){ cout<< "purr\n"; }
};

struct Lol{
  Omg* omg;
  /*...*/
  void purr(){ try{ pre(); omg-> purr(); post(); }catch(...){ error(); } }
};

또한 여러 클래스 간에 서로 다른 측면을 공유하기 위해 기본 클래스에 대한 포인터를 사용합니다.

이 접근 방식의 단점은 라이브러리 사용자가 실행될 모든 측면을 고려해야 하지만 자신의 클래스만 볼 수 있다는 것입니다.부작용이 있는지 문서를 검색해야 합니다.

cpp 파일 내부에 impl->Purr에 대한 호출을 배치한다는 것은 앞으로 헤더 파일을 변경하지 않고도 완전히 다른 작업을 수행할 수 있음을 의미합니다.아마도 내년에는 대신 호출할 수 있는 도우미 메서드를 발견하여 impl->Purr을 전혀 사용하지 않고 직접 호출하도록 코드를 변경할 수 있습니다.(예, 실제 impl::Purr 메소드도 업데이트하여 동일한 결과를 얻을 수 있지만 이 경우 다음 함수를 차례로 호출하는 것 외에는 아무 것도 달성하지 못하는 추가 함수 호출이 필요합니다.)

이는 또한 헤더에 정의만 있고 더 깔끔한 분리를 위한 구현이 없다는 것을 의미합니다. 이것이 관용구의 전체 요점입니다.

지난 며칠 동안 첫 번째 pimpl 클래스를 구현했습니다.나는 Borland Builder에 있는 Winsock2.h를 포함하여 내가 겪었던 문제를 제거하기 위해 이를 사용했습니다.구조체 정렬을 망치는 것처럼 보였고 클래스 개인 데이터에 소켓 항목이 있었기 때문에 이러한 문제는 헤더를 포함하는 모든 cpp 파일로 확산되었습니다.

pimpl을 사용함으로써, Winsock2.h는 단 하나의 cpp 파일에만 포함되어 문제를 해결할 수 있었고 문제가 다시 발생할 것을 걱정하지 않았습니다.

원래 질문에 대답하기 위해 호출을 pimpl 클래스로 전달할 때 내가 찾은 이점은 pimpl 클래스가 pimpl하기 전의 원래 클래스와 동일하고 구현이 2에 걸쳐 분산되지 않는다는 것입니다. 이상한 방식으로 수업을 듣습니다.단순히 pimpl 클래스로 전달하기 위해 public을 구현하는 것이 훨씬 더 명확합니다.

Nodet 씨가 말했듯이 하나의 클래스, 하나의 책임입니다.

이것이 언급할 가치가 있는 차이인지는 모르겠습니다만...

자체 네임스페이스에 구현을 갖고 사용자가 보는 코드에 대한 공개 래퍼/라이브러리 네임스페이스를 가질 수 있습니까?

catlib::Cat::Purr(){ cat_->Purr(); }
cat::Cat::Purr(){
   printf("purrrrrr");
}

이런 방식으로 모든 라이브러리 코드는 cat 네임스페이스를 사용할 수 있으며 사용자에게 클래스를 노출해야 하는 경우 catlib 네임스페이스에 래퍼를 만들 수 있습니다.

나는 pimpl 관용구가 얼마나 잘 알려져 있음에도 불구하고 실제 생활에서 자주 나타나는 것을 보지 못했다는 것을 알 수 있습니다(예:오픈 소스 프로젝트에서).

나는 종종 "혜택"이 과장된 것인지 궁금합니다.예, 구현 세부 사항 중 일부를 더욱 숨길 수 있고 헤더를 변경하지 않고도 구현을 변경할 수 있지만 이것이 실제로 큰 이점인지는 확실하지 않습니다.

즉, 구현이 필요하다는 것이 확실하지 않습니다. 저것 잘 숨겨져 있으며 사람들이 실제로 구현만 변경하는 경우는 매우 드뭅니다.예를 들어 새 메소드를 추가해야 하는 즉시 헤더를 변경해야 합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top