Pimpl

 


 

1. 《Effective Mocern C++》的作者在“item 22: Pimpl Idiom”的理解:

      已經整理:  https://blog.csdn.net/qq_35865125/article/details/103837518

 

2. 《API Design for C++》的作者Martin Reddy在 “3.1 PIMPL IDIOM”的理解:

The term pimpl was first introduced by Jeff Sumner as shorthand for “pointer to implementation”(Sutter, 1999). This technique can be used as a way to avoid exposing private details in your public header files . It is therefore an important mechanism to help you maintain a strong separation between your API’s interface and implementation (Sutter and Alexandrescu, 2004).

While pimpl is not strictly a design pattern (it’s a workaround to C++ specific limitations), it is an idiom that can
be considered a special case of the Bridge design pattern.


2.1 PIMPL IDIOM

----靠的是opaque pointer來隱藏實現細節到cpp文件:

 


例子:

// autotimer.h
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/time.h>
#endif
#include <string>
class AutoTimer
{
public:
    /// Create a new timer object with a human-readable name
    explicit AutoTimer(const std::string &name);
    /// On destruction, the timer reports how long it was alive
    ~AutoTimer();
private:
    // Return how long the object has been alive
    double GetElapsed() const;
    std::string mName;
#ifdef _WIN32
    DWORD mStartTime;
#else
    struct timeval mStartTime;
#endif
};

該類用於打印對象從創建到銷燬的存活時間。

該類的缺點: it includes platform-specific defines and it makes the underlying implementation details of how the timer is stored on different platforms visible to anyone looking at the header file。

改進: Hide all of the private members in the .cpp file. Then you wouldn’t need to include any of those bothersome platform specifics. The pimpl idiom lets you do this by placing all of the private members into a class (or struct) that is forward declared in the header but defined in the .cpp file.

// autotimer.h
#include <string>
class AutoTimer
{
public:
	explicit AutoTimer(const std::string &name);
	~AutoTimer();
private:
/*However, declaring it to be private imposes the limitation that only
the methods of AutoTimer can access members of the Impl. Other classes or free functions in the .cpp file will not be able to access Impl*/
	class Impl; //該聲明也可以是放在public區域,看需求
	Impl *mImpl;
};

改進後的額外工作:

AutoTimer constructor must now allocate an object of type AutoTimer::Impl and then destroy it in its destructor.  Also, all private members must be accessed via the mImpl pointer.  However, for most practical cases, the benefit of presenting a clean implementation-free API far outweighs this cost。

於是乎,改進後cpp file如下:(在cpp文件聲明class Impl, 該類嵌套在class AutoTimer內部)

// autotimer.cpp
#include "autotimer.h"
#include <iostream>
#if _WIN32
#include <windows.h>
#else
#include <sys/time.h>
#endif
class AutoTimer::Impl
{
public:
	double GetElapsed() const
	{
#ifdef _WIN32
		return (GetTickCount() - mStartTime) / 1e3;
#else
		struct timeval end_time;
		gettimeofday(&end_time, NULL);
		double t1 = mStartTime.tv_usec / 1e6 þ mStartTime.tv_sec;
		double t2 = end_time.tv_usec / 1e6 þ end_time.tv_sec;
		return t2 - t1;
#endif
	}
	std::string mName;
#ifdef _WIN32
	DWORD mStartTime;
#else
	struct timeval mStartTime;
#endif
};

AutoTimer::AutoTimer(const std::string &name) :
mImpl(new AutoTimer::Impl())
{
	mImpl->mName = name;
#ifdef _WIN32
	mImpl->mStartTime = GetTickCount();
#else
	gettimeofday(&mImpl->mStartTime, NULL);
#endif
}
AutoTimer::~AutoTimer()
{
	std::cout << mImpl->mName << ": took " << mImpl->GetElapsed()
	<< " secs" << std::endl;
	delete mImpl;	
	mImpl=NULL;
}

 


使用該模式時的注意事項:

不要把virtual 函數放到Impl類裏!You can’t hide private virtual methods in the implementation class. These must appear in the public class so that any derived classes are able to override them
 


2.2 Copy Semantics

----注意編譯器默認提供的複製構造函數和默認賦值函數,因爲上例的類中定義了指針成員。

A C++ compiler will create a copy constructor and assignment operator for your class if you don’t explicitly define them. However, these default constructors will only perform a shallow copy of your object. This is bad for pimpled classes because it means that if a client copies your object then both objects will point to the same implementation object, Impl. However, both objects will attempt to delete this same Impl object in their destructors, which will most likely lead to a crash. Two options for dealing with this are as follow.
1. Make your class uncopyable.

If you don’t intend for your users to create copies of an object, then you can declare the object to be non-copyable. You can do this by explicitly declaring a copy constructor and assignment operator.   You don’t have to provide implementations for these;
just the declarations are sufficient to prevent the compiler from generating its own default versions. 

Declaring these as private is also a good idea so that attempts to copy an object will generate a compile error rather than a link error.
2. Explicitly define the copy semantics.

If you do want your users to be able to copy your pimpled objects, then you should declare and define your own copy constructor and assignment operator. These can then perform a deep copy of your object, that is, create a copy of the Impl object instead of just copying the pointer.

2.3 Pimpl and Smart Pointers
 

《Effective Mocern C++》裏講得更好:  https://blog.csdn.net/qq_35865125/article/details/103837518

 

2.4  Advantages of Pimpl
 

# Information hiding. Private members are now completely hidden from your public interface. This allows you to keep your implementation details hidden (and proprietary in the case of closed-source APIs). It also means that your public header files are cleaner and more clearly express the true public interface.

As a result, they can be read and digested more easily by your users. One further benefit of information hiding is that your users cannot use dirty tactics as easily to gain access to your private members, such as doing the following, which is actually legal in C++ (Lakos, 1996):


#define private public // make private members be public!
#include "yourapi.h" // can now access your private members
#undef private // revert to default private semantics

--------------太TM有才了!

• Reduced coupling.  As shown in the AutoTimer example earlier, without pimpl, your public
header files must include header files for all of your private member variables. In our example,
this meant having to include windows.h or sys/time.h. This increases the compile-time coupling
of your API on other parts of the system. Using pimpl, you can move those dependencies into the
.cpp file and remove those elements of coupling.


• Faster compiles.  Another implication of moving implementation-specific includes to the .cpp
file is that the include hierarchy of your API is reduced. This can have a very direct effect on
compile times (Lakos, 1996). I will detail the benefits of minimizing include dependencies in
the performance chapter.


• Greater binary compatibility.  The size of a pimpled object never changes because your object
is always the size of a single pointer. Any changes you make to private member variables (recall
that member variables should always be private) will only affect the size of the implementation
class that is hidden inside of the .cpp file. This makes it possible to make major implementation
changes without changing the binary representation of your object.


• Lazy Allocation. The mImpl class can be constructed on demand. This may be useful if the class
allocates a limited or costly resources such as a network connection.
 

2.5 Disadvantages of Pimpl


The primary disadvantage of the pimpl idiom is that you must now allocate and free an additional implementation object for every object that is created. This increases the size of your object by the size of a pointer and may introduce a performance hit for the extra level of pointer indirection required to access all member variables, as well as the cost for additional calls to new and delete.

If you are concerned with the memory allocator performance, then you may consider using the “Fast Pimpl” idiom (Sutter, 1999) where you overload the new and delete operators for your Impl class to use a more efficient small-memory fixed-size allocator.

There is also the extra developer inconvenience to prefix all private member accesses with something like mImpl->. This can make the implementation code harder to read and debug due to the additional layer of abstraction. This becomes even more complicated when the Impl class has a pointer back to the public class.

You must also remember to define a copy constructor or disable copying of the class.

However, these inconveniences are not exposed to users of your API and are therefore not a concern from the point of view of your API’s design. They are a burden that you the developer must shoulder in order that all of your users receive a cleaner and more efficient
API. To quote a certain science officer and his captain: “The needs of the many outweigh the needs of the few. Or the one.”

One final issue to be aware of is that the compiler will no longer catch changes to member variables within const methods. This is because member variables now live in a separate object. Your compiler will only check that you don’t change the value of the mImpl pointer in a const method, but not whether you change any members pointed to by mImpl. In effect, every member function of a pimpled class could be defined as const (except of course the constructor or destructor). This is demonstrated by the following const method that legally changes a variable in the Impl object:
void PimpledObject::ConstMethod() const
{
    mImpl->mName = "string changed by a const method";
}
 

2.6 Opaque Pointers in C


While I have focused on C++ so far, you can create opaque pointers in plain C too. The concept is the same: you create a pointer to a struct that is only defined in a .c file. The following header file demonstrates what this might look like in C:
/* autotimer.h */
/* declare an opaque pointer to an AutoTimer structure */

typedef struct AutoTimer *AutoTimerPtr;

/* functions to create and destroy the AutoTimer structure */
AutoTimerPtr AutoTimerCreate();
void AutoTimerDestroy(AutoTimerPtr ptr);

#include "autotimer.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#if _WIN32
#include <windows.h>
#else
#include <sys/time.h>
#endif
struct AutoTimer
{
	char *mName;
#if _WIN32
	DWORD mStartTime;
#else
	struct timeval mStartTime;
#endif
} AutoTimer;

AutoTimerPtr AutoTimerCreate(const char *name)
{
	AutoTimerPtr ptr ¼ malloc(sizeof(AutoTimer));
	if (ptr)
	{
	ptr->mName ¼ strdup(name);
#if _WIN32
	ptr->mStartTime ¼ GetTickCount();
#else
	gettimeofday(&ptr->mStartTime, NULL);
#endif
	}
	return ptr;
}

static double GetElapsed(AutoTimerPtr ptr)
{
#if _WIN32
	return (GetTickCount() - ptr->mStartTime) / 1e3;
#else
	struct timeval end_time;
	gettimeofday(&end_time, NULL);
	double t1 ¼ ptr->mStartTime.tv_usec / 1e6 þ
	ptr->mStartTime.tv_sec;
	double t2 ¼ end_time.tv_usec / 1e6 þ end_time.tv_sec;
	return t2 - t1;
#endif
}
void AutoTimerDestroy(AutoTimerPtr ptr)
{
	if (ptr)
	{
		printf("%s: took %f secs\n", ptr->mName, GetElapsed(ptr));
		free(ptr);
	}
}


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章