Summary
Suppress the desire to separate languages: C ++ specifies Private Members as inaccessible but not as invisible. Although it has its own advantages, it can be considered throughPimplThis is often used to make private members truly invisible, so as to implement the compiler firewall and increase the degree of information hiding (terms 11 and 41 ).
Discussion
If it makes sense to create a "compiler firewall" to completely isolate the private part of the calling code and class, you should usePimplIdiom: Hide them behind an opaque pointer (that is, pointer to a declared but undefined class, it is better to use an appropriate smart pointer ). For example:
Class map {
//...
PRIVATE:
Struct impl;
Shared_ptr <impl> pimpl _;
};
Should be usedPimplTo store all private members, including member data and private member functions. This allows us to freely change the private Implementation Details of the class without re-compiling the calling code-independence and freedom are the characteristics of this idiom (Clause 41 ).
Note: we must use two declarations as shown above to declarePimpl. Merge two rows into one statement, that is, useStruct impl * pimplThis form is also valid, but the meaning is different.ImplIt is in the peripheral scope, rather than the nested type in the class.
UsePimplThere are at least three reasons, and they all come from the accessibility of the C ++ language (whether or not they can call or use something) and visibility (whether it can be seen and thus dependent on its definition. In particular, all private members of a class are not accessible except member functions and friends, but they are visible to the whole world-all code that can see the definition of a class.
The first consequence of such a difference is the potential for a longer build time because it is necessary to handle unnecessary Type Definitions. Types of private data members stored by values, parameters of Private member functions accepted by values, or types of private member functions used in visible functionsMust be definedEven if this compilation unit is not required at all, this will lead to a longer Compilation Time, for example:
Class C {
//...
PRIVATE:
Acomplicatedtype act _;
};
Header files including class C definitions must also be# IncludeIncludeAcomplicatedtypeThe defined header file.AcomplicatedtypeEvery header file that may be needed, and then, process it like thisAcomplicatedtypeHeader file. If the header file is very extensive, the Compilation Time will be significantly affected.
The second consequence of this difference is the ambiguity and name hiding of the Code that tries to call a function. Even if private member functions cannot be called outside the class and class, they are indeed involved in name search and reload parsing [todo, resolution standard translation]. therefore, the call cannot or has ambiguity. C ++ is performing an access checkBeforePerform name search and reload resolution. This is why visibility is given priority:
Int twice (INT); // 1
Class calc {
Public:
String twice (string); // 2
PRIVATE:
Char * twice (char *); // 3
Int test (){
ReturnTwice (21);//A: Wrong. 2 and 3 won't work (1 should be feasible,
} // But it cannot be considered because it is hidden)
};
Calc C;
C.Twice ("hello ");//B: Error. 3 cannot be accessed (2 should be OK
// But it can be considered because 3 is a better match)
InAThe solution is to explicitly limit the call, that is: Twice (21)To select a global function for name search. InBThe solution is to explicitly add the conversion, that isC. Twice (string ("hello "))To forcibly select the required function for reload parsing. Some of these call problems can be usedPimplFor example, you will never write the private overload of member functions, but not allPimplThis is an emergency solution for call problems that can be solved by conventional usage. [my translation is "but not all problems that can be solved by conventional pimpl use do have such an emergency solution ", which one is better?].
The third consequence of this difference is the impact on error handling and security. ConsiderationsTom CargillOfWidgetExample:
Class widget {//...
Public:
Widget & operator = (const widget &);
PRIVATE:
T1 _;
T2 _;
};
In short, if the T1 or T2 operation fails to be irreversible, we cannot writeOperator =To provide (basic) Warranties with strong or even minimum requirements (cla71 ). Fortunately, the next simple change will always provide at least basic guarantee for the value assignment operation of [error-safe] When an error occurs.T1OrT2Operations (especially constructor and destructor) have no side effects. Generally, they provide strong guarantee that Member objects are saved through pointers rather than values, preferably all hidden inPimplPointer.
Class widget {//...
Public:
Widget & operator = (const widget &);
PRIVATE:
Struct impl;
Shared_ptr <impl> pimpl _;
};
Widget & Widget: Operator = (const widget &){
Shared_ptr <impl> temp (New impl (/*...*/));
// Change temp-> T1 _ and temp-> T2 _. If the call fails, an exception is thrown. Otherwise, the request is submitted. Use:
Pimpl _ = temp;
Return * this;
}
Exceptions
Complexity can be added only when it is clear that adding indirect layers does have advantages,PimplThe same is true (terms 6 and 8 ).
Reference
[Coplien92] § 5. 5
[Dewhurst03] § 8
[Lakos96] § 6. 4.2
[Meyers97] § 34
[Murray93] § 3. 3
[Stroustrup94] § 2. 10, § 24. 4.2
[Suter00] § 23, § 26-30
[Suter02] § 18, § 22
[Suter04] § 16-17