In the C ++ program design, there are some typical design and development mechanisms that need to be discussed in detail. Here, we will give you a lot of advice to help you build your own skills.
1. Introduction
In the standard C ++ library, we can see this phenomenon:
Six public virtual functions, all of which are std: exception: what () and their overloading.
142 non-public virtual functions.
What is the purpose of this design? Why do I set the virtual function as non-public?
This is what the NVI mechanism requires: declare virtual functions as non-public functions, and declare public functions as non-virtual functions-select either virtual function or public function.
2. Mechanism Analysis
Programmers often use the virtual functions in the base class to provide an interface definition (virtual credit) and provide its implementation (a specific implementation ).
1: class Base {
2: public:
3: virtual void Foo (int ){
4: cout <"Bases Foo! "<Endl;
5 :};
6:}; the problem lies in "at the same time"-a form that defines the interface and a default implementation. Obviously, this design does not split the interface definition and implementation. At this time, we can use the idea of the template method mode:
1: class Base {
2: public:
3: void Foo (){
4: DoFoo1 ();
5: DoFoo2 ();
6:} // use DoFooX ()
7: private:
8: virtual void DoFoo1 (){
9: cout <"Bases DoFoo1" <endl;
10 :}
11: virtual void DoFoo2 (){
12: cout <"Bases DoFoo2" <endl;
13 :}
14 :};
15:
16: class Derived: public Base {
17: private:
18: virtual void DoFoo1 (){
19: cout <"Deriveds DoFoo1" <endl;
20 :};
21 :}; the function Foo defines the interface form, while the DoFooX () function customizes the behavior of the Foo function and achieves the separation of interface definition and implementation, here is an example to illustrate the benefits: if we want to implement lock unlocking control for the CS (Critical Section) in Foo:
If we complete this interface and implement separation, we can add the required process at the base class interface, and the subclass does not need to be modified:
1: class Base {
2: public:
3: void Foo (){
4: cout <"Locking" <endl;
5: DoFoo1 ();
6: DoFoo2 ();
7: cout <"Unlocking" <endl;
8:} // use DoFooX ()
9: private:
10: virtual void DoFoo1 (){
11: cout <"Bases DoFoo1" <endl;
12 :}
13: virtual void DoFoo2 (){
14: cout <"Bases DoFoo2" <endl;
15 :}
16:
17 :};
18:
19: class Derived: public Base {
20: private:
21: virtual void DoFoo1 (){
22: cout <"Deriveds DoFoo1" <endl;
23 :};
24:}; if the interface and implementation separation are not implemented, You need to modify from the base class to the subclass:
1: class Base {
2: public:
3: virtual void Foo (){
4: cout <"Locking" <endl;
5: cout <"Bases Foo" <endl;
6: cout <"Unlocking" <endl;
7 :}
8 :};
9:
10: class Derived: public Base {
11: public:
12: virtual void Foo (){
13: cout <"Locking" <endl;
14: cout <"Deriveds Foo" <endl;
15: cout <"Unlocking" <endl;
16 :};
17:}; note that the virtual function is set to protected only when the subclass needs to call the virtual function of the base class (otherwise there is no permission), and The NVI mechanism is not applicable to the destructor, for destructor, if it is set to public, it should be set to virtual (in the base class that allows deletion of polymorphism ), otherwise, it is set to private or protected in a non-virtual form (excluding the base class of the polymorphism deletion ).
Risks:
The first is the FBC problem (Fragile Base Class). Below is an example:
1: class Set {
2: std: set <int> s _;
3: public:
4: void add (int I ){
5: s _. insert (I );
6: add_impl (I); // Note virtual call.
7 :}
8: void addAll (int * begin, int * end ){
9: s _. insert (begin, end); // --------- (1)
10: addAll_impl (begin, end); // Note virtual call.
11 :}
12: private:
13: virtual void add_impl (int I) = 0;
14: virtual void addAll_impl (int * begin, int * end) = 0;
15 :};
16: class CountingSet: public Set {
17: private:
18: int count _;
19: virtual void add_impl (int I ){
20: count _ ++;
21 :}
22: virtual void addAll_impl (int * begin, int * end ){
23: count _ + = std: distance (begin, end );
24 :}
25 :}; if we modify the addAll function in the parent class and call the add function again for the numbers from begin to end, the sub-class function is disordered-the sub-class count will double the number of records (because in the sub-class, add_impl will count one at a time, and addAll_impl will count the whole one at a time ). Therefore, to prevent FBC, a public non-virtual function calls a private virtual function.
Second, performance considerations. After all, there is an additional layer of function calls. In this regard, reference 2 states: "a word about efficiency: No, none is lost in practice because if the public function is a one-line passthrough declared inline, all compilers I know of will optimize it away entirely, leaving no overhead. (Indeed, some compilers will always make such a function inline and eliminate it, whether you personally really wanted it to or not, but that's another story .)"
3. Summary
Put the NVI mechanism in your mind. If you still don't understand it, a story may be more suitable for you.
1. Objective C ++ Item 35 Consider Alternatives To Virtual Functions
2. http://www.gotw.ca/publications/mill18.htm
3. http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.3
4. http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.4
5. http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-Virtual_Interface