Details on the accessibility of C++ object oriented design

  • 2020-05-27 06:48:20
  • OfStack

preface

Recently watching Scott Meyers great god's Effective C + + "and" More Effective C + + ", although the two books are the vintage teaching reference (of course on 11 / C C + + + + 14 updates by author "Modern Effective C + +" has been sold in English, but haven't translation version), but it seems still benefit bandit shallow, and with a better understanding of this complex language and practice experience of the project increases, A lot of things and the author had a kind of resonance, before all kinds of doubts suddenly have a fog and see the sun, suddenly clear feeling, no wonder is listed as qualified C++ programmers must read books. In fact, C++ is indeed a terrible language, so there are many kinds of teachers for this language in the market. Of course, they are of different levels. As mentioned above, Meyers3 can last for a long time, which also highlights the real value of these classical teachers.

As for recent regression C + + nature, mainly think now the background and development of RPC, MQ, distributed system while called these miracle, but as a mature component is most companies can be directly to, of course, there is no denying the fact of its precious experience, because the recent online using these components or more or less a lot of problems, later can go less pit, however this kind of thing is to find; On the contrary, the use of C++ language itself takes up most of the programmer's work, which directly affects the quality of the project and its subsequent maintainability. In this, hou jie's teacher not in the floating sand building platform is still such as a warning words ringing in the ears, a qualified programmer its solid basic skills is how important.

C++ there are too many object-oriented things: public, protected, private access and inheritance, virtual and polymorphic, polyinheritance, plus const, default parameters, name lookup, etc. The permutation and combination of these elements alone can lead to many cases. It seems flexible, but not every case is worth trying.

1. public inheritance

public inheritance means the relationship of "is-a". Each object of derived type is also an object of base class type. All operations supported by base class are supported by derived class, but the derived class is more specific than the base class.


class Bird { ... };
class FlyingBird: public Bird {
public:
 virtual void fly(); ...
};
class Penguin: public Bird { ... };

So, in general, public inheritance is a relatively strict contractual relationship. Of course, public inheritance is a general concept, which is also subdivided into interface inheritance, implementation inheritance, interface and implementation inheritance.

If the base class declares an pure virtual function, the purpose is for the derived class to inherit only the function interface. If the base class declares an impure virtual function, the derived class inherits the function's interface and its default implementation. If a member function is an non-virtual function, it means that it is not intended to behave differently in a derived class, that is, the derived class inherits the function interface and a mandatory implementation.

The interface declaration for the pure virtual function makes little sense, and the non-virtual members are also obvious. But for impure virtual virtual functions, seemingly provides a default implementation would be more convenient to use, and the derived class can override the implementation is more flexible, but if use this approach directly, so if the base class for a new derived classes, but just forget about this impure derived class override virtual function, and the default implementation does not satisfy the new derived class's behavior, the new call of a derived class object will cause problems. So if you want to inherit the interface while providing a default implementation, it's a good idea to separate the two functions, provide the interface with one pure virtual function, and provide the default implementation with one non-virtual protected function, and let the derived class manually confirm whether to use the default behavior.


class Airplane {
public: virtual void fly(const Airport& dest) = 0;
protected: void defaultFly(const Airport& dest){ ... }
};

In addition to handling the default implementation of impure virtual as described above, you can also convert it to: Still use pure virtual function declarations interface, different definitions as well as its default, such derived classes in this pure override virtual interface when can completely redefined fly behavior, 1 statement may also directly in the base class name directly call the base class's default implementation (Airplane: : fly), its advantage is that don't have to introduce a new function name, the disadvantage is that the default implementation became public.

At this point, you should understand the behavior of interface inheritance in C++.

2. Alternatives to virtual functions

Previously, we talked about the NVI approach in C++ virtual function accessibility, which is a powerful alternative to public virtual, but we know that it also USES virtual functions. Virtual functions have runtime overhead, and their implementation is also compile-time determined runtime selection, and in some cases their flexibility is limited.

The Strategy policy pattern is more flexible than the customization of virtual functions to behave according to derived types. By saving a function pointer inside an object (or more generically boost::function Function object), whose behavior can be differentiated by object rather than by derived type, and even its behavior can be changed at run time through the Set interface. Although Meyers states that if you use non-member functions, you will not be able to access private members of the class by default, otherwise you will need to compromise encapsulation to a certain extent boost::function+boost::bind This powerful tool is also convenient to use the member functions in the inheritance system.

I feel here, although the design pattern has been regarded as the classic book by C + + development, but with Modern C + + on the standard to introduce more features and functions, C + + development will surely become more friendly and intuitive, nor would it be too by the classical 23 type, after all, most of the design patterns are implemented through inheritance, inevitably increase the complexity of application development and maintenance.

3. Other problems arising from the inheritance system

Well, the fun and games are over, and here's the dark hour in C++ history.

3.1 visibility of inherited names

C++ has a set of rules for name lookup, which in general is the search order from local to peripheral, from derived classes to base classes, and from inner namespaces to global namespaces.

Since we haven't explained the case of function overloading so far, you are still in peace: for public non-virtual we don't rewrite, for virtual we can override, which is fine, but once you consider the overloading problem of the same function name, C++ has a set of theories that will make you confused: C++ prevents new derived classes created in an application library or application framework from being loaded with overloaded functions inherited from an estranged base class, so C++ does not automatically import the name of the base class into the derived class during inheritance.

Ok, this means that, before the inherited interface, is also in use in the derived class scope had not found the symbol, and find the symbol in the base class after meet calls, and if you are in the base class defines its an overloaded versions (both virtual non - virtual), C + + when name lookup in your derived class scope to find the name, and then type checking and overloading, but the overloaded version only to appear in the derived class's version, the base class version does not participate in overload!!!!!!

So, if you want to add or override an overloaded version of the virtual or non-virtual function in a derived class, the first thing is to declare all versions of the base class symbol into the derived name scope using the using declaration, and then do the rest.

3.2 never redefine the inherited non-virtual function

The C++ non-virtual functions are statically bound at compile time, and their name lookup begins with the static type of their pointer.

Under no circumstances should an inherited non-virtual function be redefined, otherwise the version of its call will depend on its pointer static type, which is contrary to the 1 relation of public inherited is-a.

3.3 never redefine inherited default parameter values

Since we said above that we should not redefine an inherited non-virtual function, we can say that we should never redefine the default values of an inherited virtual function with default values. The reason: the virtual function is dynamically bound, and the default parameters are statically bound.

Therefore, if the default values of the parameters of the base and derived classes are different from 1, then the static binding of the parameters with the reference and pointer calls and the dynamic binding of the calling function body will be very weird, so you need to avoid this situation. In addition, if the virtual function parameter is the default value specified by the base class, and the derived class override does not specify the default value of the parameter, then if the client side calls the function in the form of a derived class object, static binding will occur and the specified parameter value needs to be displayed. If the client calls the function with a new-style pointer, reference, what happens is dynamic binding, and you don't specify its parameters with default values.


class Shape {
public: virtual void draw(ShapeColor color = Red) const = 0; ...
};
class Circle: public Shape {
//  If called in object mode draw , must specify color The default parameter cannot be used 
public: virtual void draw(ShapeColor color) const; ... 
};

One way to solve this problem is to use the NVI approach, whose public non-virtual interface provides default defaults (and is not overridden by derived classes), while private virtual does not use the default default features to avoid this possible inconsistency.

3.4 private inheritance

private inherit no is - a "contractual relationship", is a huge difference in use: the compiler will no longer automatically converts a derived class object to a base class object, which means that the original parameters will no longer be able to receive a base class object function for its transfer the derived class object as an argument (object, reference and pointer types are not allowed, the compiler will be submitted to the base class S is access to the base class for derived class T); Meanwhile, all members inherited from the base class will become the access rights of private in the derived class.

private inheritance means that only the implementation is inheritance, interface section is omitted entirely, so private inheritance should be used the base class of some help to improve its derived class functions, in some cases, says has "has - a" consistent with the type, so in addition to considering the derived class needs to access base class members and virtual protected factor is involved, you should try to use combination type instead of private inheritance, but even so, highly can also use the following methods:


class Timer { 
public: virtual void onTick() const; ... 
};
class Widget {
private:
 class WidgetTimer: public Timer {
 public: virtual void OnTick() const; ...
 };
 WidgetTimer timer;
};

As for protected inheritance, I haven't even used Meyers, so why should I waste my brain thinking about him...

reference

Effective C++

conclusion


Related articles: