1

I have the following piece of code (from Koening & Moo Accelerated C++ page 255) that defines a generic handle class Handle. Handle is used to manage the memory of objects. However, there is an aspect of the code I don't quite follow.

template <class T>
class Handle
{
  public:
    Handle() : p(0) {}
    Handle &operator=(const Handle &);
    ~Handle() { delete p; }

    Handle(T *t) : p(t) {}

  private:
    T *p;
};

template <class T>
Handle<T> &Handle<T>::operator=(const Handle &rhs)
{
    if (&rhs != this)
    {
        delete p;
        p = rhs.p ? rhs.p->clone() : 0;
    }
    return *this;
};

class Base
{
    friend class Handle<Base>;

  protected:
    virtual Base *clone() const { return new Base; }

  private:
    int a;
};

main()
{
    Handle<Base> h;
    h = new Base;

    return 0;
}

When we overload = why is the argument rhs of type const Handle (Handle<T> &Handle<T>::operator=(const Handle &rhs)) when the right-hand side in the assignment in main is of type Base* (h = new Base)? Shouldn't the argument rather be (const T &rhs) to accomodate the type that we assign to h?


If I replace const Handle &rhs by const T &rhs then the program compiles - but I get a long list of errors starting with

*** Error in `./main': double free or corruption (fasttop): 0x000055aca1402c20 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x790cb)[0x7f16c54020cb]
....
N08
  • 1,265
  • 13
  • 23
  • https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three – Oliver Charlesworth Aug 16 '17 at 07:28
  • _when the right-hand side in the assignment in main is of type `Base`_ nope it's of type `Base*`. That said check the constructor now ;-) – muXXmit2X Aug 16 '17 at 07:28
  • In both cases: Use a debugger to step through your code and you will find out what actually happens. – Rene Aug 16 '17 at 07:31
  • @muXXmit2X The constructor `Handle(T *t)` describes what happens when we use `h(new Base)`. Isn't this different from h = new Base? – N08 Aug 16 '17 at 07:38
  • @N08 When you do `h = new Base;`, there is an implicit conversion, so this basically becomes `h = Handle(new Base);`, and thus the copy-assignment operator is called. – Holt Aug 16 '17 at 07:39
  • @Holt Thanks. I wish I could upvote, but I have too few points for that – N08 Aug 16 '17 at 07:56

1 Answers1

1

new Base is implicitly converted to Handle via Handle(T *t) constructor, and then assigned to h.

Implicit conversion from h = new Base works like this:

h = Handle<Base>(new Base);

I updated your example with some prints to illustrate this point: demo.

Dev Null
  • 4,731
  • 1
  • 30
  • 46
  • But the constructor `Handle(T *t)` describes what happens when we use `h(new Base)` - this is different from `h = new Base` (?) – N08 Aug 16 '17 at 07:31
  • See output of [demo](http://coliru.stacked-crooked.com/a/f6140bb0bb2e14d9): in short, `h = new Base` is expanded into `h = Handle(new Base)`. – Dev Null Aug 16 '17 at 07:37
  • Thanks, a good explanation. This may be off-topic, but why is it necessary for us to use `clone` in `p = rhs.p ? rhs.p->clone() : 0;`? Why can't we simply say `p = rhs.p;` given that `rhs.p` already contains a `new Base`? – N08 Aug 16 '17 at 08:12
  • Oh, I see now: It is because the `rhs` is destroyed after the assignment, so that is why we can't have the pointer point to `rhs.p` – N08 Aug 16 '17 at 08:52
  • If you want to reuse internals of on object that is going to be destroyed now, you will need to use C++>=11 and define move constructor/move assignment operator like this `Handle(Handle &&other) : p(other.p) {other.p = nullptr; }` and `Handle &operator=(Handle &&other) { swap(p, other.p); }` – Dev Null Aug 16 '17 at 10:36