From cb5bab9ae57de3d5e2de56f19e7dde1d411f884f Mon Sep 17 00:00:00 2001 From: Bjarne Stroustrup Date: Wed, 20 Jul 2016 15:35:41 -0400 Subject: [PATCH] C.129 rationale and examples added --- CppCoreGuidelines.md | 214 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 207 insertions(+), 7 deletions(-) diff --git a/CppCoreGuidelines.md b/CppCoreGuidelines.md index ae3646e..5868841 100644 --- a/CppCoreGuidelines.md +++ b/CppCoreGuidelines.md @@ -1,6 +1,6 @@ # 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. ## 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) +* ??? + ### 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. # 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: