Topic: Q: Prevent refinement of a class?


Author: AllanW@my-dejanews.com
Date: 1998/10/17
Raw View
In article <3625DB31.5B75@uk.geopak-tms.com>,
  Joachim.Fabini@ericsson.com wrote:
[snip]
> If you are designing a class that you do not
> intend to be used as a base class, you have two options:
> 1.  Declare the constructor(s) private and provide a
>     static method to create new instances on the heap.
>     This will programmatically prevent any derivation.
[Example of class Point with a static member function create()]

Now you can't have any auto Point variables. You can't use placement
new (or even regular new!), so the STL containers are out. You can't
return your class object by value unless you have a public copy
constructor -- but oops, that's a public constructor! If you do that
and you don't have code reviews then "clever" (read: naughty)
programmers could try to "get around the problem":

    // Note: Never do this
    class Dot : public Point {
        static Point &pt;
        int radius;
        Dot() : Point(pt), radius(0) {}
        // More functionality for Dot...
    };
    // Leaks *one* Point no matter how many Dots are created
    Point & Dot::pt = * Point::create();

One thing in your defense, though -- how did the programmer know that
there was a copy constructor, even though there was no default
constructor? He read the header file. The header file should have a
comment. Even if it had only a ONE-LINE comment:
    // WARNING -- DO NOT DERIVE FROM THIS CLASS!
In seven of the United States, that warning above would be enough
justification to fatally shoot the head of the "clever" programmer.
The other 43 states just haven't had enough experience yet...

> 2.  Give the class a non-virtual destructor and state
>     in the class documentation that the class has not
>     been designed for use as a base class.
> The second approach has the drawback that, as there is
> nothing programmatically to disallow derivation, we
> cannot prevent derivation taking place.

This is what I generally do. It's unfortunate, though; errors caught
by a compiler are MUCH cheaper than errors caught in Q/A or by a
customer.

> If a derived
> class is created that needs to release heap based
> resources, it will be susceptible to memory leaks.

That depends on how it's used, doesn't it?

> 12.1.4 Design Every Class so that it may Serve as a
>        Base Class
> This realisation that neither of the two approaches
> above may be ideal, leads to a third option: all
> classes should be designed so they are suitable for
> use as a base class.  If this third option is adopted,
> it implies that destructors should always be virtual.

Allowing it to be a base class does not neccesarily imply this.
It depends on how the derived class is to be used.

There aren't *THAT* many design patterns that can end up doing
this:
    void func2(Base*b) {
        // ...
        delete b;
    }
There are slightly more design patterns that can end up doing
this:
    Base*item[100];
    int i;
    for (i=0; i<100; ++i)
        switch (something) {
            case 0: item[i] = new Base; break;
            case 1: item[i] = new Der1; break;
            case 2: item[i] = new Der2; break;
        }
    // ... use item[] as needed ...
    for (i=0; i<100; ++i) delete item[i];
This is more realistic -- but it's usually central to the program's
algorithm. In other words, Base was designed *SPECIFICALLY* for
derivation.

Other cases of derived classes are designed "merely" for code reuse.
For instance:
    func() {
        Der d;
        // ... use d, possibly including:
        Somefunc(d); // Reads data from, or writes data to, object d
        // ...
    }
Here we create a derived class item. Some of d's functionality is
inherited from Base, but we don't need to know that to use d. It's
even possible that Somefunc() was designed to work on a Base
object, but this isn't a problem because it doesn't destroy d.
Within func(), we *know* that d is a Der object, and depending on
the compiler we might even be able to optimize away "virtualness."
And when func() is finished with d, it destroys it properly, even
if Base doesn't have a virtual destuctor.

> The above quote fails to consider another option as stated
> by Cargill in "C++ Programming Style" (page 207):
> "If a public base class does not have a virtual destructor,
> no derived class nor members of a derived class should have
> a destructor"

Again, this depends on the problem domain.

> He also states: "If a multiple inheritance hierarchy has
> any destructors, every base class should have a virtual
> destructor".

This one I agree with. But please remember that not all potential
base classes are also potentially part of an MI hierarchy.

Also, be careful how you read that. I know programmers that feel
they should give a destructor (not neccesarily virtual, mind you)
to every class that has a constructor. They often end up writing
empty destructors:
    ~MyClass() { }
This is just silly. At least they (usually) make it inline, so
there's no real harm being done. But still...

--
AllanW@my-dejanews.com is a "Spam Magnet" -- never read.
Please reply in USENET only, sorry.

-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/       Search, Read, Discuss, or Start Your Own
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Joachim Fabini <Joachim.Fabini@ericsson.no.spam.com>
Date: 1998/10/14
Raw View
Does the C++ standard provide mechanisms to mark
a class as non-refinable (i.e. no class is allowed
to derive from it)?

I am thinking about a mechanism similar to the
one provided in Java by use of the keyword "final".

--Joachim
------------------------------------------------------------
Joachim Fabini
email: Joachim.Fabini@ericsson.no.spam.com

Please remove "no.spam" from address when replying by email.


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Anatoli <REMOVETHIS.anatoli@ptc.com>
Date: 1998/10/14
Raw View
Joachim Fabini wrote:
>
> Does the C++ standard provide mechanisms to mark
> a class as non-refinable (i.e. no class is allowed
> to derive from it)?
>
> I am thinking about a mechanism similar to the
> one provided in Java by use of the keyword "final".


It is not clear why would you want this, but anyway...

class DoNotDeriveFromMe
{
  friend class MyFinalClass;
  DoNotDeriveFromMe () {}
};

class MyFinalClass : virtual DoNotDeriveFromMe
{
};

class Bar : public MyFinalClass // won't compile
{
};

--
Regards
Anatoli (anatoli at ptc dot com) -- opinions aren't


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: paul@ra.avid.com (Paul Miller)
Date: 1998/10/15
Raw View
Joachim Fabini <Joachim.Fabini@ericsson.no.spam.com> writes:

>Does the C++ standard provide mechanisms to mark
>a class as non-refinable (i.e. no class is allowed
>to derive from it)?

Yes, declare your class constructor and destructor private,
and provide a static allocation function for your class (so
that you can actually make one). If the constructor or
destructor are private, you will not be able to subclass from
it.

You can also make the constructor/destructor protected to
guarantee that it only functions as a base class - ie. that
it MUST be derived from.

--
Paul T. Miller                | paul@elastic.avid.com
Principal Engineer            | Opinions expressed here are my own.
Avid Technology, Inc. - Graphics and Effects Software Group
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Jerry Leichter <leichter@smarts.com>
Date: 1998/10/15
Raw View
| Does the C++ standard provide mechanisms to mark
| a class as non-refinable (i.e. no class is allowed
| to derive from it)?
|
| I am thinking about a mechanism similar to the
| one provided in Java by use of the keyword "final".

Not directly.  There is a hack method for getting the same effect:  Give
the class a private destructor.  Since any subclass must implicitly call
its superclass's destructor - which won't be accessible - such
subclasses are forbidden.

Using this hack has a significant side-effect (which, in fact, is why it
was proposed to begin with):  If a class has a private destructor, it
cannot be allocated on the stack, statically, or contained in some other
class - only explicitly using new; nor can an object of that class be
explicitly deleted.  In all these cases, there would be an implicit call
to the destructor:  At the end of the stack scope, at program exit, or
at the delete statement.  The way you get rid of one of these objects is
to provide a "deleteMe()" member, which simply does a "delete this" - it
can do so since, as a member function, it has access to the private
destructor.

Hmm.  That raises an interesting point.  Is the following legal?

class okToDerive;

class noDerive
{ friend class okToDerive;

 ~noDerive()
 {};

public:
 deleteMe()
 { delete this; }
};

class okToDerive : noDerive
{ ... };

As far as I can see, it is - so not only can you prohibit derivation in
general, you can limit it to an explicit list of classes.  What's
interesting is that stack instances of okToDerive should be permissible.

Note:  Could a C++ compiler, like a Java compiler, use this information
to generate better code?  In theory, yes; in practice, since it's a hack
that many are likely to see as an abuse of the language, it's not likely
to be very high on anyone's list of optimizations.
       -- Jerry
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Paul Grealish <paul.grealish@uk.geopak-tms.com>
Date: 1998/10/15
Raw View
[ moderator's note: This discussion has moved away from the
  C++ standard to programming style. I've redirected followups
  to comp.lang.c++ only. -sdc ]

Joachim Fabini wrote:
>
> Does the C++ standard provide mechanisms to mark
> a class as non-refinable (i.e. no class is allowed
> to derive from it)?

No, not directly.


> I am thinking about a mechanism similar to the
> one provided in Java by use of the keyword "final".

My company's development standard makes some points
on this subject, that might provide some food for
thought:

"12.1 Should Destructors Always be Declared Virtual?

12.1.1 The Problem
C++ provides no simple mechanism (unlike Java's 'final')
for declaring that a class should not be used as a base
class.  If you are designing a class that you do not
intend to be used as a base class, you have two options:
1.  Declare the constructor(s) private and provide a
    static method to create new instances on the heap.
    This will programmatically prevent any derivation.
2.  Give the class a non-virtual destructor and state
    in the class documentation that the class has not
    been designed for use as a base class.

12.1.2 Programmatically Disallow Derivation
This approach has the problem that it makes the class
interface much more clumsy for client code.  Client
code will be forced to take responsibility for free-ing
up memory, and will be forced to deal with pointers
to what may be a very simple class.  Imagine the code
for a point class:

 Point* pP1 = Point::create();
 Point* pP2 = Point::create();
 pP1->setCoords(123, 456);
 *pP2 = *pP1;
 delete pP1;
 delete pP2;

Of course we'd use a smart pointer of the appropriate
type to handle the deallocation, but do we really want
to force our clients to write code like this?

12.1.3 Hope that other Programmers take Notice of the
       Class Documentation
The second approach has the drawback that, as there is
nothing programmatically to disallow derivation, we
cannot prevent derivation taking place.  If a derived
class is created that needs to release heap based
resources, it will be susceptible to memory leaks.

12.1.4 Design Every Class so that it may Serve as a
       Base Class
This realisation that neither of the two approaches
above may be ideal, leads to a third option: all
classes should be designed so they are suitable for
use as a base class.  If this third option is adopted,
it implies that destructors should always be virtual.
Some coding standards I've seen actually state this
is a design rule.  The problem is that by providing
a virtual destructor, you're advertising the fact that
your class is suitable for use as a base class.  This
advertisement implies that you've given careful
consideration to each of the class methods, deciding
which should be virtual; which should be pure virtual
with no implementation; which should be pure virtual
with an implementation; and which should be non-virtual.
This is all well and good if that's what you've actually
done, but in most cases you won't have done this as you
didn't really intend your class to be used as a base
class.  So what should you do?"

The above quote fails to consider another option as stated
by Cargill in "C++ Programming Style" (page 207):
"If a public base class does not have a virtual destructor,
no derived class nor members of a derived class should have
a destructor"
He also states: "If a multiple inheritance hierarchy has
any destructors, every base class should have a virtual
destructor".

HTH,

 Paul
--
+---------------------------------+
|         Paul Grealish           |
|       GEOPAK-TMS Limited        |
|       Cambridge, England        |
| paul.grealish@uk.geopak-tms.com |
+---------------------------------+


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]