rationale and examples added
This commit is contained in:
Bjarne Stroustrup 2016-07-20 15:35:41 -04:00
parent df88742f7d
commit cb5bab9ae5

View File

@ -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: