Martin said:
Example code in one of my books intrigues me:
class B {
public:
B* Clone() const {
B* p = DoClone();
assert(typeid(*p) == typeid(*this));
return p;
}
protected:
B(const B&);
private:
virtual B* DoClone() const = 0;
};
This will force all derived classes to implement DoClone, or else Clone in
the base class will fail in the assert. Could someone please explain why?
Well, suppose you have some X class you can call Clone() on an instance
of it it, and everything is cool. You get back an X object, because
there is an X::Clone(). Now you make a derived Y from X, but forget to
override Clone(). When you call Clone() on an instance of Y, you are
calling X::Clone(), which only clones the X parts! You get back an X
object that has no Y parts.
This programmer wanted to avoid that mistake. So firstly, he split the
clone function into two. A base class wrapper, and a DoClone() virtual
which should not be called directly.
Is typeid of *this for some derived class D of type D or B? I'm sure *p is
typeid uses run-time type information; it gets the actual run-time
type.
of whatever type overrides DoClone, but i'm not sure about *this in the base
class function (even though a derived type is actually in place)... (i may
It doesn't matter. Both p and this are just pointers. The p pointer is
a pointer to the newly cloned object, and this is a pointer to the
object that was cloned. The assertion is simply that they both have the
same type. I.e. the bug of forgetting to override DoClone() has not
occured.
Also another thing. pure virtual function is private??? What the hell??? Is
It's private to remind people that it's an internal function which
should not be called directly. If it's invoked on the base class, the
call is forbidden by access. But it can still be overriden by public
DoClone() virtuals in derived classes. All derived class writers should
remember to mark their DoClone() override private: otherwise they
expose the function for direct calling.
In a language with auxiliary methods, like Common Lisp, you would just
use an :around method. C++ doesn't have those, so if you want to wrap
additional advice methods around a method call, you split things up
into multiple functions, such as a base class wrapper that calls
virtual "helpers".
Here is how I would do exactly the same thing in that language:
(defmethod clone :around ((obj t))
(let ((new-obj (call-next-method)))
(assert (eq (class-of new-obj) (class-of obj))
new-obj))
The :around keyword specifies that this is an "around" auxiliary
method, and (obj t) means we have one argument called obj, which is of
class T. T is the superclass of all classes. I.e. this method is at the
root of the class hierarchy. Whenever the primary CLONE method is
called, this thing gets control first.
We call the primary CLONE method using (call-next-method), which is a
special function in the object system that an around method to pass
control down the chain. If we don't invoke this, the primary CLONE will
never be called, and around methods can choose to do that.
We catch the cloned object, bind it to the local variable NEW-OBJ, and
then assert that it has exactly the same class as OBJ.
We can now write a couple of primary methods to test. How about one
specialized for integers:
(defmethod clone ((obj integer))
obj)
we "clone" an integer just by returning it. Integers are immutable
objects, so we can return the same object and say we have cloned it.
This works just fine:
(clone 42) --> 42
Now let's write a broken CLONE, which takes a string:
(defmethod clone ((obj string))
(list 1 2)) ;; wrong!
It returns a list instead of cloning the string:
(clone "abc") --> *assertion!*
Boom, the assertion goes off, signaling a condition. Under CLISP the
assertion condition has this text:
"(EQ (CLASS-OF NEW-OBJ) (CLASS-OF OBJ)) must evaluate to a non-NIL
value."
There is no way for the programmer to call the primary CLONE method
directly; it's under the control of the method combination system.
That part is simulated in C++ by renaming the primary function to
DoClone, and then documenting that as being an internal interface, and
please would everyone go through Clone.
So when I saw this C++ class, I right away identified it as a clumsy
around method type of thingy.
it really standard compliant? Can you override it in derived classes
(portably according to the standard) and still keep it private in derived
classes? Does it mean that virtuality of a function applies no matter whta
it's access qualifier is???
Access specifiers don't control visibility, only access. You can
inherit a private virtual function, and override it with a public one.
The two are identified as the same function based on their name and
type signature.
When you write a DoClone() in a base class, its subject to whatever
access specifier comes before it. It's just a new function that shadows
the base class DoClone(). The virtuality kicks in separately: because
the base class is virtual, and a shadowing function has the same type
signature, the shadowing one becomes an override.
Access specifiers only control which call sites are permitted to access
the member, not the semantics of inheritance and virtuals.
The real protection in this example comes not from the private:
specifier, but from the renaming of Clone to DoClone(). The derived
class writers can easily forget to make their DoClone() private, which
allows it to be called directly (but not through the base class, where
DoClone is private!)
The users of the class have to cooperate and go through Clone().