mirror of
https://github.com/isocpp/CppCoreGuidelines.git
synced 2025-12-17 20:54:41 +03:00
C.129
rationale and examples added
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# <a name="main"></a>C++ Core Guidelines
|
# <a name="main"></a>C++ Core Guidelines
|
||||||
|
|
||||||
July 19, 2016
|
July 20, 2016
|
||||||
|
|
||||||
Editors:
|
Editors:
|
||||||
|
|
||||||
@@ -3537,7 +3537,7 @@ The "helper functions" have no need for direct access to the representation of a
|
|||||||
|
|
||||||
##### Note
|
##### 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
|
##### Enforcement
|
||||||
|
|
||||||
@@ -3644,7 +3644,7 @@ Prefer the order `public` members before `protected` members before `private` me
|
|||||||
|
|
||||||
##### Enforcement
|
##### Enforcement
|
||||||
|
|
||||||
???
|
Flag protected data.
|
||||||
|
|
||||||
## <a name="SS-concrete"></a>C.concrete: Concrete types
|
## <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
|
##### 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
|
##### 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
|
##### 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
|
### <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
|
# <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:
|
Enumeration rule summary:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user