mirror of
https://github.com/isocpp/CppCoreGuidelines.git
synced 2024-03-22 13:30:58 +08:00
C.129
rationale and examples added
This commit is contained in:
parent
df88742f7d
commit
cb5bab9ae5
|
@ -1,6 +1,6 @@
|
|||
# <a name="main"></a>C++ Core Guidelines
|
||||
|
||||
July 19, 2016
|
||||
July 20, 2016
|
||||
|
||||
Editors:
|
||||
|
||||
|
@ -3537,7 +3537,7 @@ The "helper functions" have no need for direct access to the representation of a
|
|||
|
||||
##### Note
|
||||
|
||||
This rule becomes even better if C++17 gets "uniform function call." ???
|
||||
This rule becomes even better if C++ gets ["]uniform function call](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0251r0.pdf).
|
||||
|
||||
##### Enforcement
|
||||
|
||||
|
@ -3644,7 +3644,7 @@ Prefer the order `public` members before `protected` members before `private` me
|
|||
|
||||
##### Enforcement
|
||||
|
||||
???
|
||||
Flag protected data.
|
||||
|
||||
## <a name="SS-concrete"></a>C.concrete: Concrete types
|
||||
|
||||
|
@ -6005,15 +6005,214 @@ Use `virtual` only when declaring a new virtual function. Use `override` only wh
|
|||
|
||||
##### Reason
|
||||
|
||||
??? Herb: I've become a non-fan of implementation inheritance -- seems most often an anti-pattern. Are there reasonable examples of it?
|
||||
Implementation details in an interface makes the interface brittle;
|
||||
that is, makes its users vulnerable to having to recompile after changes in the implementation.
|
||||
Data in a base class increases the complexity of implementing the base and can lead to replication of code.
|
||||
|
||||
##### Note
|
||||
|
||||
Definition:
|
||||
|
||||
* interface inheritance is the use of inheritance to separate users from implementations,
|
||||
in particular to allow derived classes to be added and changed without affecting the users of base classes.
|
||||
* implementation inheritance is the use of inheritance to simplify implementation of new facilities
|
||||
by making useful operations available for implementers of related new operations (sometimes called "programming by difference").
|
||||
|
||||
A pure interface class is simply a set of pure virtual functions; see [I.25](#Ri-abstract).
|
||||
|
||||
In early OOP (e.g., in the 1980s and 1990s), implementation inheritance and interface inheritance were often mixed
|
||||
and bad habits die hard.
|
||||
Even now, mixtures are not uncommon in old code bases and in old-style teaching material.
|
||||
|
||||
The importance of keeping the two kinds of inheritance increases
|
||||
|
||||
* with the size of a hierarchy (e.g., dozens of derived classes),
|
||||
* with the length of time the hierarchy is used (e.g., decades), and
|
||||
* with the number of distinct organizations in which a hierarchy is used
|
||||
(e.g., it can be difficult to distribute an update to a base class)
|
||||
|
||||
|
||||
##### Example, bad
|
||||
|
||||
class Shape { // BAD, mixed interface and implementation
|
||||
public:
|
||||
Shape();
|
||||
Shape(Point ce = {0,0}, Color co = none): cent{ce}, col {co} { /* ... */}
|
||||
|
||||
Point center() const { return cent; }
|
||||
Color color() const { return col; }
|
||||
|
||||
virtual void rotate(int) = 0;
|
||||
virtual void move(Point p) { cent = p; redraw(); }
|
||||
|
||||
virtual void redraw();
|
||||
|
||||
// ...
|
||||
public:
|
||||
Point cent;
|
||||
Color col;
|
||||
};
|
||||
|
||||
class Circle : public Shape {
|
||||
public:
|
||||
Circle(Point c, int r) :Shape{c}, rad{r} { /* ... */ }
|
||||
|
||||
// ...
|
||||
private:
|
||||
int rad;
|
||||
};
|
||||
|
||||
class Triangle : public Shape {
|
||||
public:
|
||||
Triangle(Point p1, Point p2, Point p3); // calculate center
|
||||
// ...
|
||||
};
|
||||
|
||||
Problems:
|
||||
|
||||
* As the hierarchy grows and more data is adder to `Shape`, the constructors gets harder to write and maintain.
|
||||
* Why calculate the center for the `Triangle`? we may never us it.
|
||||
* Add a data member to `Shape` (e.g., drawing style or canvas)
|
||||
and all derived classes and all users needs to be reviewed, possibly changes, and probably recompiled.
|
||||
|
||||
The implementation of `Shape::move()` is an example of implementation inheritance:
|
||||
we have defined 'move()` once and for all for all derived classes.
|
||||
The more code there is in such base class member function implementations and the more data is shared by placing it in the base,
|
||||
the more benefits we gain - and the less stable the hierarchy is.
|
||||
|
||||
##### Example
|
||||
|
||||
???
|
||||
This Shape hierarchy can be rewritten using interface inheritance:
|
||||
|
||||
class Shape { // pure interface
|
||||
public:
|
||||
virtual Point center() const =0;
|
||||
virtual Color color() const =0;
|
||||
|
||||
virtual void rotate(int) =0;
|
||||
virtual void move(Point p) =0;
|
||||
|
||||
virtual void redraw() =0;
|
||||
|
||||
// ...
|
||||
};
|
||||
|
||||
Note that a pure interface rarely have constructors: there is nothing to construct.
|
||||
|
||||
class Circle : public Shape {
|
||||
public:
|
||||
Circle(Point c, int r, Color c) :cent{c}, rad{r}, col{c} { /* ... */ }
|
||||
|
||||
Point center() const override { return cent; }
|
||||
Color color() const override { return col; }
|
||||
|
||||
// ...
|
||||
private:
|
||||
Point cent;
|
||||
int rad;
|
||||
Color col;
|
||||
};
|
||||
|
||||
The interface is now less brittle, but there is more work in implementing the member functions.
|
||||
For example, `center` has to be implemented by every class derived from `Shape`.
|
||||
|
||||
##### Example, dual hierarchy
|
||||
|
||||
How can we gain the benefit of the stable hierarchies from implementation hierarchies and the benefit of implementation reuse from implementation inheritance.
|
||||
One popular technique is dual hierarchies.
|
||||
There are many ways of implementing the ide of dual hierarchies; here, we use a multiple-inheritance variant.
|
||||
|
||||
First we devise a hierarchy of interface classes:
|
||||
|
||||
class Shape { // pure interface
|
||||
public:
|
||||
virtual Point center() const =0;
|
||||
virtual Color color() const =0;
|
||||
|
||||
virtual void rotate(int) =0;
|
||||
virtual void move(Point p) =0;
|
||||
|
||||
virtual void redraw() =0;
|
||||
|
||||
// ...
|
||||
};
|
||||
|
||||
class Circle : public Shape { // pure interface
|
||||
public:
|
||||
int radius() = 0;
|
||||
// ...
|
||||
};
|
||||
|
||||
To make this interface useful, we must provide its implementation classes (here, named equivalently, but in the `Impl` namespace):
|
||||
|
||||
class Impl::Shape : public Shape { // implementation
|
||||
public:
|
||||
// constructors, destructor
|
||||
// ...
|
||||
virtual Point center() const { /* ... */ }
|
||||
virtual Color color() const { /* ... */ }
|
||||
|
||||
virtual void rotate(int) { /* ... */ }
|
||||
virtual void move(Point p) { /* ... */ }
|
||||
|
||||
virtual void redraw() { /* ... */ }
|
||||
|
||||
// ...
|
||||
};
|
||||
|
||||
Now `Shape` is a poor example of a class with an implementation,
|
||||
but bare with us because this is just a simple example of a technique aimed at more complex hierrchies.
|
||||
|
||||
class Impl::Circle : public Circle, public Impl::Shape { // implementation
|
||||
publc:
|
||||
// constructors, destructor
|
||||
|
||||
int radius() { /* ... */ }
|
||||
// ...
|
||||
};
|
||||
|
||||
And we could extend the hierarchies by adding a Smiley class (:-)):
|
||||
|
||||
class Smiley : public Circle { // pure interface
|
||||
public:
|
||||
// ...
|
||||
};
|
||||
|
||||
class Impl::Smiley : Public Smiley, public Impl::Circle { // implementation
|
||||
publc:
|
||||
// constructors, destructor
|
||||
// ...
|
||||
}
|
||||
|
||||
There are now two hierarchies:
|
||||
|
||||
* interface: Smiley -> Circle -> Shape
|
||||
* implementation: Impl::Smiley -> Impl::Circle -> Impl::Shape
|
||||
|
||||
Since each implementation derived from its inteface as well as its implementation base class we get a latice (DAG):
|
||||
|
||||
Smiley -> Circle -> Shape
|
||||
^ ^ ^
|
||||
| | |
|
||||
Impl::Smiley -> Impl::Circle -> Impl::Shape
|
||||
|
||||
As mentioned, this is just one way to construct a dual hierarchy.
|
||||
|
||||
Another (related) technique for separating interface and implementation is [PIMPL](#???).
|
||||
|
||||
##### Note
|
||||
|
||||
There is often a choice between offering common functionality as (implemented) base class funcetions and free-standing functions
|
||||
(in an implementation namespace).
|
||||
Base classes gives a shorter notation and easier access to shared data (in the base)
|
||||
at the cost of the functionality being available only to users of the hierarchy.
|
||||
|
||||
##### Enforcement
|
||||
|
||||
???
|
||||
* Flag a derived to base conversion to a base with both data and virtual functions
|
||||
(except for calls from a derived class memvber to a base class member)
|
||||
* ???
|
||||
|
||||
|
||||
### <a name="Rh-copy"></a>C.130: Redefine or prohibit copying for a base class; prefer a virtual `clone` function instead
|
||||
|
||||
|
@ -7015,7 +7214,8 @@ Naked unions are a source of type errors.
|
|||
|
||||
# <a name="S-enum"></a>Enum: Enumerations
|
||||
|
||||
Enumerations are used to define sets of integer values and for defining types for such sets of values. There are two kind of enumerations, "plain" `enum`s and `class enum`s.
|
||||
Enumerations are used to define sets of integer values and for defining types for such sets of values.
|
||||
There are two kind of enumerations, "plain" `enum`s and `class enum`s.
|
||||
|
||||
Enumeration rule summary:
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user