IT story

pImpl 관용구가 실제로 실제로 사용됩니까?

hot-time 2020. 6. 6. 08:10
반응형

pImpl 관용구가 실제로 실제로 사용됩니까?


Herb Sutter의 "Exceptional C ++"책을 읽고 있는데,이 책에서 pImpl 관용구에 대해 배웠습니다. 기본적으로 아이디어는 private객체의 구조를 만들고 class동적으로 할당 하여 컴파일 시간단축하고 더 나은 방식으로 개인 구현을 숨기는 것입니다.

예를 들면 다음과 같습니다.

class X
{
private:
  C c;
  D d;  
} ;

다음과 같이 변경할 수 있습니다.

class X
{
private:
  struct XImpl;
  XImpl* pImpl;       
};

그리고 CPP에서 정의는 다음과 같습니다.

struct X::XImpl
{
  C c;
  D d;
};

이것은 꽤 흥미로운 것처럼 보이지만, 내가 일한 회사 나 소스 코드를 본 오픈 소스 프로젝트에서는 이전에 이런 종류의 접근 방식을 본 적이 없습니다. 그래서이 기술이 실제로 실제로 사용되는 것이 궁금합니다.

어디서나주의해서 사용해야합니까? 이 기술을 임베디드 시스템 (성능이 매우 중요한 곳)에서 사용하는 것이 좋습니다?


그래서이 기술이 실제로 실제로 사용되는 것이 궁금합니다. 어디서나주의해서 사용해야합니까?

물론 사용됩니다. 나는 거의 모든 수업에서 내 프로젝트에서 사용합니다.


PIMPL 관용구를 사용하는 이유 :

이진 호환성

라이브러리를 개발할 때 XImpl클라이언트와 바이너리 호환성 을 유지 하지 않고도 필드를 추가 / 수정할 수 있습니다 (충돌을 의미합니다). X클래스에 새 필드를 추가 할 때 클래스 의 이진 레이아웃이 변경되지 않으므로 Ximpl부 버전 업데이트에서 라이브러리에 새 기능을 추가하는 것이 안전합니다.

물론, 당신은 또한 새로운 공개 / 개인 가상이 아닌 방법을 추가 할 수 있습니다 X/ XImpl이진 호환성을 깨지 않고 있지만, 표준 헤더 / 구현 기술로 파에 그의.

데이터 숨기기

라이브러리, 특히 독점 라이브러리를 개발하는 경우 라이브러리의 공용 인터페이스를 구현하는 데 사용 된 다른 라이브러리 / 구현 기술을 공개하지 않는 것이 좋습니다. 지적 재산권 문제로 인해 또는 사용자가 구현에 대해 위험한 가정을 취하려고 유혹하거나 끔찍한 캐스팅 트릭을 사용하여 캡슐화를 깨뜨릴 수 있다고 생각하기 때문입니다. PIMPL은이를 해결 / 완화합니다.

컴파일 시간

X필드 및 / 또는 메소드를 XImpl클래스에 추가 / 제거 할 때 소스 파일 (구현) 파일 만 다시 빌드하면 되므로 컴파일 시간이 단축됩니다 (표준 기술에서 개인 필드 / 메소드 추가에 맵핑 됨). 실제로는 일반적인 작업입니다.

표준 헤더 / 구현 기술 (PIMPL 제외)을 사용하여에 새 필드를 추가하면 X할당 X크기를 조정해야하므로 스택 (또는 스택 또는 힙)에 할당 한 모든 클라이언트를 다시 컴파일해야합니다. X 할당하지 않은 모든 클라이언트 다시 컴파일해야하지만 오버 헤드 일뿐입니다 (클라이언트 측의 결과 코드는 동일합니다).

또한 캡슐화 이유로 인해이 메소드를 호출 할 수는 없지만 XClient1.cpp개인용 메소드 X::foo()를 추가 X하고 X.h변경 한 경우에도 표준 헤더 / 구현 분리 를 다시 컴파일해야합니다 XClient1.cpp! 위와 같이 순수한 오버 헤드이며 실제 C ++ 빌드 시스템 작동 방식과 관련이 있습니다.

물론, 메소드의 구현을 수정하는 경우 (헤더를 건드리지 않기 때문에) 재 컴파일은 필요하지 않지만 표준 헤더 / 구현 기술과 동등합니다.


이 기술을 임베디드 시스템 (성능이 매우 중요한 곳)에서 사용하도록 권장합니까?

그것은 당신의 목표가 얼마나 강력한 지에 달려 있습니다. 그러나이 질문에 대한 유일한 답은 얻는 것과 잃는 것을 측정하고 평가하는 것입니다. 또한 클라이언트가 임베디드 시스템에서 사용할 라이브러리를 게시하지 않으면 컴파일 시간 이점 만 적용된다는 점을 고려하십시오!


많은 라이브러리가 API에서 안정적으로 유지하기 위해 적어도 일부 버전에서는 사용하는 것으로 보입니다.

그러나 모든 것에 관해서는주의를 기울이지 않고 어디서나 아무것도 사용해서는 안됩니다. 항상 사용하기 전에 생각하십시오. 그것이 당신에게주는 이점과 그것이 지불 할만한 가치가 있는지 평가하십시오.

이 장점 당신에게은 다음과 같습니다 :

  • 공유 라이브러리의 이진 호환성 유지에 도움
  • 특정 내부 세부 사항 숨기기
  • 재 컴파일주기 감소

Those may or may not be real advantages to you. Like for me, I don't care about a few minutes recompilation time. End users usually also don't, as they always compile it once and from the beginning.

Possible disadvantages are (also here, depending on the implementation and whether they are real disadvantages for you):

  • Increase in memory usage due to more allocations than with the naïve variant
  • increased maintenance effort (you have to write at least the forwarding functions)
  • performance loss (the compiler may not be able to inline stuff as it is with a naïve implementation of your class)

So carefully give everything a value, and evaluate it for yourself. For me, it almost always turns out that using the pimpl idiom is not worth the effort. There is only one case where I personally use it (or at least something similar):

My C++ wrapper for the linux stat call. Here the struct from the C header may be different, depending on what #defines are set. And since my wrapper header can't control all of them, I only #include <sys/stat.h> in my .cxx file and avoid these problems.


Agree with all the others about the goods, but let me put in evidence a limit: doesn't work well with templates.

The reason is that template instantiation requires the full declaration available where the instantiation took place. (And that's the main reason you don't see template methods defined into CPP files)

You can still refer to templetised subclasses, but since you have to include them all, every advantage of "implementation decoupling" on compiling (avoiding to include all platoform specific code everywhere, shortening compilation) is lost.

Is a good paradigm for classic OOP (inheritance based) but not for generic programming (specialization based).


Other people have already provided the technical up/downsides, but I think the following is worth noting:

First and foremost, don't be dogmatic. If pImpl works for your situation, use it - don't use it just because "it's better OO since it really hides implementation" etc. Quoting the C++ FAQ:

encapsulation is for code, not people (source)

Just to give you an example of open source software where it is used and why: OpenThreads, the threading library used by the OpenSceneGraph. The main idea is to remove from the header (e.g. <Thread.h>) all platform-specific code, because internal state variables (e.g. thread handles) differ from platform to platform. This way one can compile code against your library without any knowledge of the other platforms' idiosyncrasies, because everything is hidden.


I would mainly consider PIMPL for classes exposed to be used as an API by other modules. This has many benefits, as it makes recompilation of the changes made in the PIMPL implementation does not affect the rest of the project. Also, for API classes they promote a binary compatibility (changes in a module implementation do not affect clients of those modules, they don't have to be recompiled as the new implementation has the same binary interface - the interface exposed by the PIMPL).

As for using PIMPL for every class, I would consider caution because all those benefits come at a cost: an extra level of indirection is required in order to access the implementation methods.


I think this is one of the most fundamental tools for decoupling.

I was using pimpl (and many other idioms from Exceptional C++) on embedded project (SetTopBox).

The particular purpose of this idoim in our project was to hide the types XImpl class uses. Specifically we used it to hide details of implementations for different hardware, where different headers would be pulled in. We had different implementations of XImpl classes for one platform and different for the other. Layout of class X stayed the same regardless of the platfrom.


I used to use this technique a lot in the past but then found myself moving away from it.

Of course it is a good idea to hide the implementation detail away from the users of your class. However you can also do that by getting users of the class to use an abstract interface and for the implementation detail to be the concrete class.

The advantages of pImpl are:

  1. Assuming there is just one implementation of this interface, it is clearer by not using abstract class / concrete implementation

  2. If you have a suite of classes (a module) such that several classes access the same "impl" but users of the module will only use the "exposed" classes.

  3. No v-table if this is assumed to be a bad thing.

The disadvantages I found of pImpl (where abstract interface works better)

  1. Whilst you may have only one "production" implementation, by using an abstract interface you can also create a "mock" inmplementation that works in unit testing.

  2. (The biggest issue). Before the days of unique_ptr and moving you had restricted choices as to how to store the pImpl. A raw pointer and you had issues about your class being non-copyable. An old auto_ptr wouldn't work with forwardly declared class (not on all compilers anyway). So people started using shared_ptr which was nice in making your class copyable but of course both copies had the same underlying shared_ptr which you might not expect (modify one and both are modified). So the solution was often to use raw pointer for the inner one and make the class non-copyable and return a shared_ptr to that instead. So two calls to new. (Actually 3 given old shared_ptr gave you a second one).

  3. Technically not really const-correct as the constness isn't propagated through to a member pointer.

In general I have therefore moved away in the years from pImpl and into abstract interface usage instead (and factory methods to create instances).


As many other said, the Pimpl idiom allows to reach complete information hiding and compilation independency, unfortunately with the cost of performance loss (additional pointer indirection) and additional memory need (the member pointer itself). The additional cost can be critical in embedded software development, in particular in those scenarios where memory must be economized as much as possible. Using C++ abstract classes as interfaces would lead to the same benefits at the same cost. This shows actually a big deficiency of C++ where, without recurring to C-like interfaces (global methods with an opaque pointer as parameter), it is not possible to have true information hiding and compilation independency without additional resource drawbacks: this is mainly because the declaration of a class, which must be included by its users, exports not only the interface of the class (public methods) needed by the users, but also its internals (private members), not needed by the users.


Here is an actual scenario I encountered, where this idiom helped a great deal. I recently decided to support DirectX 11, as well as my existing DirectX 9 support, in a game engine. The engine already wrapped most DX features, so none of the DX interfaces were used directly; they were just defined in the headers as private members. The engine utilizes DLLs as extensions, adding keyboard, mouse, joystick, and scripting support, as week as many other extensions. While most of those DLLs did not use DX directly, they required knowledge and linkage to DX simply because they pulled in headers that exposed DX. In adding DX 11, this complexity was to increase dramatically, however unnecessarily. Moving the DX members into a Pimpl defined only in the source eliminated this imposition. On top of this reduction of library dependencies, my exposed interfaces became cleaner as moved private member functions into the Pimpl, exposing only front facing interfaces.


It is used in practice in a lot of projects. It's usefullness depends heavily on the kind of project. One of the more prominent projects using this is Qt, where the basic idea is to hide implementation or platformspecific code from the user (other developers using Qt).

This is a noble idea but there is a real drawback to this: debugging As long as the code hidden in private implemetations is of premium quality this is all well, but if there are bugs in there, then the user/developer has a problem, because it just a dumb pointer to a hidden implementation, even if he has the implementations source code.

So as in nearly all design decisions there a pros and cons.


One benefit I can see is that it allows the programmer to implement certain operations in a fairly fast manner:

X( X && move_semantics_are_cool ) : pImpl(NULL) {
    this->swap(move_semantics_are_cool);
}
X& swap( X& rhs ) {
    std::swap( pImpl, rhs.pImpl );
    return *this;
}
X& operator=( X && move_semantics_are_cool ) {
    return this->swap(move_semantics_are_cool);
}
X& operator=( const X& rhs ) {
    X temporary_copy(rhs);
    return this->swap(temporary_copy);
}

PS: I hope I'm not misunderstanding move semantics.

참고URL : https://stackoverflow.com/questions/8972588/is-the-pimpl-idiom-really-used-in-practice

반응형