IT박스

“PIMPL”관용구를 사용해야하는 이유는 무엇입니까?

itboxs 2020. 7. 5. 08:15
반응형

“PIMPL”관용구를 사용해야하는 이유는 무엇입니까? [복제]


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

백그 라운더 :

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.
  • 그런 다음 책임을 혼합하지 않기 때문에 하나의 클래스 구현, 하나의 클래스 전달.

대부분의 사람들이 이것을 핸들 바디 관용구라고합니다. James Coplien의 책 Advanced C ++ Programming Styles and Idioms ( Amazon 링크 )를 참조하십시오. 루이스 캐롤의 성격 때문에 미소 만 남을 때까지 사라지는 Cheshire Cat 이라고도합니다 .

예제 코드는 두 개의 소스 파일 세트에 분산되어야합니다. 그런 다음 Cat.h 만 제품과 함께 제공되는 파일입니다.

CatImpl.h is included by Cat.cpp and CatImpl.cpp contains the implementation for CatImpl::Purr(). This won't be visible to the public using your product.

Basically the idea is to hide as much as possible of the implementation from prying eyes. This is most useful where you have a commercial product that is shipped as a series of libraries that are accessed via an API that the customer's code is compiled against and linked to.

We did this with the rewrite of IONAs Orbix 3.3 product in 2000.

As mentioned by others, using his technique completely decouples the implementation from the interface of the object. Then you won't have to recompile everything that uses Cat if you just want to change the implementation of Purr().

This technique is used in a methodology called design by contract.


For what is worth, it separates the implementation from the interface. This is usually not very important in small size projects. But, in large projects and libraries, it can be used to reduce the build times significantly.

Consider that the implementation of Cat may include many headers, may involve template meta-programming which takes time to compile on its own. Why should a user, who just wants to use the Cat have to include all that? Hence, all the necessary files are hidden using the pimpl idiom (hence the forward declaration of CatImpl), and using the interface does not force the user to include them.

I'm developing a library for nonlinear optimization (read "lots of nasty math"), which is implemented in templates, so most of the code is in headers. It takes about five minutes to compile (on a decent multi-core CPU), and just parsing the headers in an otherwise empty .cpp takes about a minute. So anyone using the library has to wait a couple of minutes every time they compile their code, which makes the development quite tedious. However, by hiding the implementation and the headers, one just includes a simple interface file, which compiles instantly.

It does not necessarily have anything to do with protecting the implementation from being copied by other companies - which wouldn't probably happen anyway, unless the inner workings of your algorithm can be guessed from the definitions of the member variables (if so, it is probably not very complicated and not worth protecting in the first place).


If your class uses the pimpl idiom, you can avoid changing the header file on the public class.

This allows you to add/remove methods to the pimpl class, without modifying the external class's header file. You can also add/remove #includes to the pimpl too.

When you change the external class's header file, you have to recompile everything that #includes it (and if any of those are header files, you have to recompile everything that #includes them, and so on)


Typically, the only reference to Pimpl class in the header for the Owner class (Cat in this case) would be a forward declaration, as you have done here, because that can greatly reduce the dependencies.

For example, if your Pimpl class has ComplicatedClass as a member (and not just a pointer or reference to it) then you would need to have ComplicatedClass fully defined before it's use. In practice, this means including "ComplicatedClass.h" (which will also indirectly include anything ComplicatedClass depends on). This can lead to a single header fill pulling in lots and lots of stuff, which is bad for managing your dependencies (and your compile times).

When you use the pimpl idion, you only need to #include the stuff used in the public interface of your Owner type (which would be Cat here). Which makes things better for people using your library, and means you don't need to worry about people depending on some internal part of your library - either by mistake, or because they want to do something you don't allow so they #define private public before including your files.

If it's a simple class, there's usually no reason to use a Pimpl, but for times when the types are quite big, it can be a big help (especially in avoiding long build times)


Well, I wouldn't use it. I have a better alternative:

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;
}

Does this pattern have a name?

As an also Python and Java programmer, I like this a lot more than the pImpl idiom.


We use PIMPL idiom in order to emulate aspect oriented programming where pre, post and error aspects are called before and after the execution of a member function.

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

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

We also use pointer to base class to share different aspects between many classes.

The drawback of this approach is that the library user has to take into account all the aspects that are going to be executed, but only sees his class. It requires browsing the documentation for any side-effects.


Placing the call to the impl->Purr inside the cpp file means that in the future you could do something completely different without having to change the header file. Maybe next year they discover a helper method they could have called instead and so they can change the code to call that directly and not use impl->Purr at all. (Yes, they could achieve the same thing by updating the actual impl::Purr method as well but in that case you are stuck with an extra function call that achieves nothing but calling the next function in turn)

It also means the header only has definitions and does not have any implementation which makes for a cleaner separation, which is the whole point of the idiom.


I just implemented my first pimpl class over the last couple of days. I used it to eliminate problems I was having including winsock2.h in Borland Builder. It seemed to be screwing up struct alignment and since I had socket things in the class private data, those problems were spreading to any cpp file that included the header.

By using pimpl, winsock2.h was included in only one cpp file where I could put a lid on the problem and not worry that it would come back to bite me.

To answer the original question, the advantage I found in forwarding the calls to the pimpl class was that the pimpl class is the same as what your original class would have been before you pimpl'd it, plus your implementations aren't spread over 2 classes in some weird fashion. It's much clearer to implement the publics to simply forward to the pimpl class.

Like Mr Nodet said, one class, one responsibility.


I don't know if this is a difference worth mentioning but...

Would it be possible to have the implementation in its own namespace and have a public wrapper / library namespace for the code the user sees:

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

This way all library code can make use of the cat namespace and as the need to expose a class to the user arises a wrapper could be created in the catlib namespace.


I find it telling that, in spite of how well-known the pimpl idiom is, I don't see it crop up very often in real-life (e.g. in open source projects).

I often wonder if the "benefits" are overblown; yes, you can make some of your implementation details even more hidden, and yes, you can change your implementation without changing the header, but it's not obvious that these are big advantages in reality.

That is to say, it's not clear that there's any need for your implementation to be that well hidden, and perhaps it's quite rare that people really do change only the implementation; as soon as you need to add new methods, say, you need to change the header anyway.

참고URL : https://stackoverflow.com/questions/60570/why-should-the-pimpl-idiom-be-used

반응형