Skip to content

Commit cb5bab9

Browse files
C.129
rationale and examples added
1 parent df88742 commit cb5bab9

File tree

1 file changed

+207
-7
lines changed

1 file changed

+207
-7
lines changed

CppCoreGuidelines.md

Lines changed: 207 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# <a name="main"></a>C++ Core Guidelines
22

3-
July 19, 2016
3+
July 20, 2016
44

55
Editors:
66

@@ -3537,7 +3537,7 @@ The "helper functions" have no need for direct access to the representation of a
35373537

35383538
##### Note
35393539

3540-
This rule becomes even better if C++17 gets "uniform function call." ???
3540+
This rule becomes even better if C++ gets ["]uniform function call](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0251r0.pdf).
35413541

35423542
##### Enforcement
35433543

@@ -3644,7 +3644,7 @@ Prefer the order `public` members before `protected` members before `private` me
36443644

36453645
##### Enforcement
36463646

3647-
???
3647+
Flag protected data.
36483648

36493649
## <a name="SS-concrete"></a>C.concrete: Concrete types
36503650

@@ -6005,15 +6005,214 @@ Use `virtual` only when declaring a new virtual function. Use `override` only wh
60056005

60066006
##### Reason
60076007

6008-
??? Herb: I've become a non-fan of implementation inheritance -- seems most often an anti-pattern. Are there reasonable examples of it?
6008+
Implementation details in an interface makes the interface brittle;
6009+
that is, makes its users vulnerable to having to recompile after changes in the implementation.
6010+
Data in a base class increases the complexity of implementing the base and can lead to replication of code.
6011+
6012+
##### Note
6013+
6014+
Definition:
6015+
6016+
* interface inheritance is the use of inheritance to separate users from implementations,
6017+
in particular to allow derived classes to be added and changed without affecting the users of base classes.
6018+
* implementation inheritance is the use of inheritance to simplify implementation of new facilities
6019+
by making useful operations available for implementers of related new operations (sometimes called "programming by difference").
6020+
6021+
A pure interface class is simply a set of pure virtual functions; see [I.25](#Ri-abstract).
6022+
6023+
In early OOP (e.g., in the 1980s and 1990s), implementation inheritance and interface inheritance were often mixed
6024+
and bad habits die hard.
6025+
Even now, mixtures are not uncommon in old code bases and in old-style teaching material.
6026+
6027+
The importance of keeping the two kinds of inheritance increases
6028+
6029+
* with the size of a hierarchy (e.g., dozens of derived classes),
6030+
* with the length of time the hierarchy is used (e.g., decades), and
6031+
* with the number of distinct organizations in which a hierarchy is used
6032+
(e.g., it can be difficult to distribute an update to a base class)
6033+
6034+
6035+
##### Example, bad
6036+
6037+
class Shape { // BAD, mixed interface and implementation
6038+
public:
6039+
Shape();
6040+
Shape(Point ce = {0,0}, Color co = none): cent{ce}, col {co} { /* ... */}
6041+
6042+
Point center() const { return cent; }
6043+
Color color() const { return col; }
6044+
6045+
virtual void rotate(int) = 0;
6046+
virtual void move(Point p) { cent = p; redraw(); }
6047+
6048+
virtual void redraw();
6049+
6050+
// ...
6051+
public:
6052+
Point cent;
6053+
Color col;
6054+
};
6055+
6056+
class Circle : public Shape {
6057+
public:
6058+
Circle(Point c, int r) :Shape{c}, rad{r} { /* ... */ }
6059+
6060+
// ...
6061+
private:
6062+
int rad;
6063+
};
6064+
6065+
class Triangle : public Shape {
6066+
public:
6067+
Triangle(Point p1, Point p2, Point p3); // calculate center
6068+
// ...
6069+
};
6070+
6071+
Problems:
6072+
6073+
* As the hierarchy grows and more data is adder to `Shape`, the constructors gets harder to write and maintain.
6074+
* Why calculate the center for the `Triangle`? we may never us it.
6075+
* Add a data member to `Shape` (e.g., drawing style or canvas)
6076+
and all derived classes and all users needs to be reviewed, possibly changes, and probably recompiled.
6077+
6078+
The implementation of `Shape::move()` is an example of implementation inheritance:
6079+
we have defined 'move()` once and for all for all derived classes.
6080+
The more code there is in such base class member function implementations and the more data is shared by placing it in the base,
6081+
the more benefits we gain - and the less stable the hierarchy is.
60096082

60106083
##### Example
60116084

6012-
???
6085+
This Shape hierarchy can be rewritten using interface inheritance:
6086+
6087+
class Shape { // pure interface
6088+
public:
6089+
virtual Point center() const =0;
6090+
virtual Color color() const =0;
6091+
6092+
virtual void rotate(int) =0;
6093+
virtual void move(Point p) =0;
6094+
6095+
virtual void redraw() =0;
6096+
6097+
// ...
6098+
};
6099+
6100+
Note that a pure interface rarely have constructors: there is nothing to construct.
6101+
6102+
class Circle : public Shape {
6103+
public:
6104+
Circle(Point c, int r, Color c) :cent{c}, rad{r}, col{c} { /* ... */ }
6105+
6106+
Point center() const override { return cent; }
6107+
Color color() const override { return col; }
6108+
6109+
// ...
6110+
private:
6111+
Point cent;
6112+
int rad;
6113+
Color col;
6114+
};
6115+
6116+
The interface is now less brittle, but there is more work in implementing the member functions.
6117+
For example, `center` has to be implemented by every class derived from `Shape`.
6118+
6119+
##### Example, dual hierarchy
6120+
6121+
How can we gain the benefit of the stable hierarchies from implementation hierarchies and the benefit of implementation reuse from implementation inheritance.
6122+
One popular technique is dual hierarchies.
6123+
There are many ways of implementing the ide of dual hierarchies; here, we use a multiple-inheritance variant.
6124+
6125+
First we devise a hierarchy of interface classes:
6126+
6127+
class Shape { // pure interface
6128+
public:
6129+
virtual Point center() const =0;
6130+
virtual Color color() const =0;
6131+
6132+
virtual void rotate(int) =0;
6133+
virtual void move(Point p) =0;
6134+
6135+
virtual void redraw() =0;
6136+
6137+
// ...
6138+
};
6139+
6140+
class Circle : public Shape { // pure interface
6141+
public:
6142+
int radius() = 0;
6143+
// ...
6144+
};
6145+
6146+
To make this interface useful, we must provide its implementation classes (here, named equivalently, but in the `Impl` namespace):
6147+
6148+
class Impl::Shape : public Shape { // implementation
6149+
public:
6150+
// constructors, destructor
6151+
// ...
6152+
virtual Point center() const { /* ... */ }
6153+
virtual Color color() const { /* ... */ }
6154+
6155+
virtual void rotate(int) { /* ... */ }
6156+
virtual void move(Point p) { /* ... */ }
6157+
6158+
virtual void redraw() { /* ... */ }
6159+
6160+
// ...
6161+
};
6162+
6163+
Now `Shape` is a poor example of a class with an implementation,
6164+
but bare with us because this is just a simple example of a technique aimed at more complex hierrchies.
6165+
6166+
class Impl::Circle : public Circle, public Impl::Shape { // implementation
6167+
publc:
6168+
// constructors, destructor
6169+
6170+
int radius() { /* ... */ }
6171+
// ...
6172+
};
6173+
6174+
And we could extend the hierarchies by adding a Smiley class (:-)):
6175+
6176+
class Smiley : public Circle { // pure interface
6177+
public:
6178+
// ...
6179+
};
6180+
6181+
class Impl::Smiley : Public Smiley, public Impl::Circle { // implementation
6182+
publc:
6183+
// constructors, destructor
6184+
// ...
6185+
}
6186+
6187+
There are now two hierarchies:
6188+
6189+
* interface: Smiley -> Circle -> Shape
6190+
* implementation: Impl::Smiley -> Impl::Circle -> Impl::Shape
6191+
6192+
Since each implementation derived from its inteface as well as its implementation base class we get a latice (DAG):
6193+
6194+
Smiley -> Circle -> Shape
6195+
^ ^ ^
6196+
| | |
6197+
Impl::Smiley -> Impl::Circle -> Impl::Shape
6198+
6199+
As mentioned, this is just one way to construct a dual hierarchy.
6200+
6201+
Another (related) technique for separating interface and implementation is [PIMPL](#???).
6202+
6203+
##### Note
6204+
6205+
There is often a choice between offering common functionality as (implemented) base class funcetions and free-standing functions
6206+
(in an implementation namespace).
6207+
Base classes gives a shorter notation and easier access to shared data (in the base)
6208+
at the cost of the functionality being available only to users of the hierarchy.
60136209

60146210
##### Enforcement
60156211

6016-
???
6212+
* Flag a derived to base conversion to a base with both data and virtual functions
6213+
(except for calls from a derived class memvber to a base class member)
6214+
* ???
6215+
60176216

60186217
### <a name="Rh-copy"></a>C.130: Redefine or prohibit copying for a base class; prefer a virtual `clone` function instead
60196218

@@ -7015,7 +7214,8 @@ Naked unions are a source of type errors.
70157214

70167215
# <a name="S-enum"></a>Enum: Enumerations
70177216

7018-
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.
7217+
Enumerations are used to define sets of integer values and for defining types for such sets of values.
7218+
There are two kind of enumerations, "plain" `enum`s and `class enum`s.
70197219

70207220
Enumeration rule summary:
70217221

0 commit comments

Comments
 (0)