tags:

views:

138

answers:

6

Hi...

Here's what I'm trying to do (this code doesn't work):

class Base
{
    virtual Base *clone() { return new Base(this); }
    virtual void ID() { printf("BASE");
};

class Derived : publc Base
{
    virtual Base *clone() { return new Derived(this); }
    virtual void ID() { printf("DERIVED"); }
}

.
.
Derived d;
Base *bp = &d;
Base *bp2 = bp->clone();

bp2->ID();

What I'd like is to see "DERIVED" printed out... what I get is "BASE". I'm a long-time C programmer, and fairly experienced with C++... but I'm not making any headway with this one... any help would be appreciated.

A: 

You're slicing the class in Base bp = &d; (this constructs a new base bp from the derived-ptr.)

Try Base* bp = &d; instead. (i.e. create a pointer of Base type to the Derived object.)

Marcus Lindblom
I don't see an edit statement on the OP (at this time), and the code is already doing as you suggest (albeit it does not compile at all). While the reported issue certainly does reek of a slicing issue, the posted code does not demonstrate it...Edit: Oops, I was looking at the wrong post for the edit mark. You are correct, Marcus. Sorry for the trouble.
Nathan Ernst
A: 

With Base bp = &d;

You've "sliced" d, so to the compiler, bp really is only of type Base, which is why when you call bp->clone() the compiler calls Base::clone(); and bp2->ID() prints BASE.

Base& bp = d; will do what you want.

Alan
A: 

Your example is incorrect and will not compile. Specifically this line:

Base bp = &d;

That may also be the root cause of your problem (you may be slicing your object), but I can't tell for certain without seeing working code.

You also have a problem where your two classes are not related (did you mean to write class Derived : public Base?)

R Samuel Klatchko
Yes, I know this code won't compile. I fixed this bug within a minute of posting it, but you guys are fast!Please see the corrected question.I don't know what "slicing" is, but I'm looking it up...
wanlessv
@wanlessv - you should always make sure your code compiles before posting a question. Without knowing exactly what you are trying to do, we can't always see what the problem is.
R Samuel Klatchko
+3  A: 

That code is riddled with syntactical errors. Perhaps most significantly, Derived doesn't inherit from Base. Secondly, aside from the syntactical errors (probably simple typos), Base obviously needs a virtual destructor. The clone method pretty much demands that you can call operator delete on a base pointer (Base*).

class Base
{
public:
    virtual ~Base() {}
    virtual Base* clone() const { return new Base(*this); }
    virtual void ID() const { printf("BASE"); }
};

class Derived: public Base
{
public:
    // [Edit] Changed return type to Derived* instead of Base*.
    // Thanks to Matthieu for pointing this out. @see comments below.
    virtual Derived* clone() const { return new Derived(*this); }
    virtual void ID() const { printf("DERIVED"); }
};

int main()
{
    Derived d;
    Base* bp = &d;

    Base* bp2 = bp->clone();
    bp2->ID(); // outputs DERIVED as expected
    delete bp2;
}
I apolgize to all... I created a simple example and posted it, trying to avoid burying everyone in the actual code... but I made several mistakes in the simple version, and before I could get back and correct them, I had several answers!At this point, I don't know which of the answers are answering my buggy example, which which still pertain to my real question...
wanlessv
It's also better to use a smart pointer (such as boost::shared_ptr) to automatically delete bp2.
Daniel James
@Daniel Yeah, but I figure if the OP has difficulty with inheritance and polymorphism, it might be a while before he can understand RAII and smart pointers.
virtual destructors are only strictly necessary if you have non-POD data members. Sorry to nitpick. Like you, however, once I worked through the myriad of syntactical errors, the snippet worked as expected.
Nathan Ernst
@Nathan That's not exactly right. Deleting through a polymorphic base pointer, regardless of whether subclasses have non-PODs as members, invokes undefined behavior according to Sutter. A base class dtor should either be public and virtual or protected and nonvirtual to avoid this. Granted we might be able to get away with it when Derived stores no PODs and has no destructor of its own, but it's still invoking undefined behavior. If Derived were to define a dtor of its own, deleting through Base* would fail to invoke the function.
With a base class essentially implementing the prototype pattern with a clone method, the code pretty much demands we have a public virtual dtor regardless of how subclasses are implemented at the moment. Doing otherwise is prone to undefined behavior-inducing, fragile code that's wrought with danger.
@stinky472, I'll eat some of my words. *In this case*, no virtual destructor is necessary, as there are absolutely no non-POD data members involved at any level. You are correct in asserting that if you delete the base pointer and a derived type has non-POD types, you are into dreaded non-deterministic behavior. Next time I see Herb, I'll buy him a cup of coffee on your behalf.
Nathan Ernst
@Nathan For practicality's sake, I think you are correct. However, if we are to get pedantic, I would modify that to "virtual dtors are only strictly necessary if we need to delete through a base pointer and the subclass requires its destructor to be called (either explicitly defined or implicitly generated)". For instance, pointers would be included among PODs and a subclass could consist of only PODs but still allocate memory in a constructor which needs to be deallocated through a dtor. In such a case, the base class still needs a virtual dtor if we're deleting through base pointers.
@stinky: return a pointer to `Derived` from `Derived::clone`: http://stackoverflow.com/questions/3063534/can-i-pass-a-pointer-to-a-superclass-but-create-a-copy-of-the-child/3063881#3063881
Matthieu M.
@Matthieu Only difference I see was the omission of clone being a const method (const incorrectness). I overlooked that when I was correcting the OP's original example and modified my post accordingly for the const-correct result. ID should also be assumed to be immutable, so I added const to that method as well for completeness. Thanks for pointing it out.
@stinky: `Derived::clone` should also return a `Derived*`, taking advantage of covariant return types to preserve as much type information as possible.
Matthieu M.
@Matthieu You're going to have to explain that one to me. AFAIK, we can't do that: you if you have Derived* Derived::clone() const, then it's not overriding the original Base* Base::clone() const method, which is the whole point of providing clone in the first place.
@Matthieu (apologies for mispelling name earlier): I edited the post once again with your suggestions. Thanks for teaching me something new!
@stinky: you're welcome, that's why we're on SO after all :)
Matthieu M.
+3  A: 

Once all the compile errors are fixed, I ended up with this:

#include <cstdio>

class Base
{
  public:
    Base() {}
    Base(const Base&) {}
    virtual Base *clone() { return new Base(*this); }
    virtual void ID() { printf("BASE"); }
};

class Derived : public Base
{
  public:
    Derived() {}
    Derived(const Derived&) {}
    virtual Base *clone() { return new Derived(*this); }
    virtual void ID() { printf("DERIVED"); }
};


int main()
{
  Derived d;
  Base *bp = &d;
  Base *bp2 = bp->clone();

  bp2->ID();
}

Which gives you what you are looking for -- DERIVED.

Fred Larson
Thank you all for your time. Studying this example, I think I understand the issue.
wanlessv
A: 

The code looks fine, other than the silly syntax typos and the missing ctors.

John