-3

Assume we have a class like this:

class C
{
public:
  C() {}
  virtual ~C() noexcept { if (c) { delete c; } }

protected:
  int a;
  float b;
  C* c;
}

How would you properly implement the copy and move constructors? Normally you would just invoke the copy constructor of the object that needs to be copied, but since it's the very same class, how would you handle this properly?

Leandros
  • 16,805
  • 9
  • 69
  • 108
  • 1
    Where is `c` initialized? – 101010 May 20 '15 at 14:20
  • Why have `c` when you have `this`? Anyway, both operators should just set `c` to point to the new object. – simon May 20 '15 at 14:20
  • How are you initializing `c`? (=> why a recursive loop? Are you supposing that you can have a cyclic path of C instances?) – peppe May 20 '15 at 14:20
  • 8
    Voted to close as unclear. I could answer for e.g. a singly linked list, which this *could* be, but there are zillions other things it *could* be. – Cheers and hth. - Alf May 20 '15 at 14:22
  • Have you tried std::move for move constructor? – sop May 20 '15 at 14:24
  • 2
    If you are going to deal with raw pointers and memory management I wouod suggest you use the [copy and swap idiom](http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom) – NathanOliver May 20 '15 at 14:24
  • 1
    It depends on what instance of C the new instance of C should point to. You need to tell us more about the problem you're trying to solve with this structure. – Rob K May 20 '15 at 15:06
  • To make the question clear: Does `C* c` point to `this`? Could it point to something that could (through a chain of `c`s) eventually point back to `this`? When you copy a `C`, do you intend to copy the entire chain of pointed to by `C* c;`s, or do you want to copy the pointer (but not the pointed to data)? These are design question that can only be guessed at. – Yakk - Adam Nevraumont May 20 '15 at 15:27
  • -1: OP came back, saw comments asking for clarification, and accepted an answer without improving the question. This means that the question will be less useful for future readers of the website. If the question is improved, please notify me with an @Yakk and I'll rescind. – Yakk - Adam Nevraumont May 20 '15 at 17:46
  • You are correct, @Yakk, I haven't provided any clarification, and I'm sorry for this, it wasn't a problem I actually had, just a thought of a scenario I wanted to have some input to. My original problem was easily solved by just checking for `NULL` pointers and not copying / moving them if they're `NULL`. – Leandros May 21 '15 at 07:13

4 Answers4

3

How would you properly implement the copy and move constructors?

Moving is easy: copy the victim's pointer to the target, then null the victim's to indicate that it no longer owns anything. And, of course, copy the other values.

For copying, you need to choose the semantics you want: unique ownership (in which case don't allow copying), shared ownership (in which case increment a reference count, and change the destructor to decrease it), deep copying (in which case allocate a new object, copying the old one), or something else.

Normally you would just invoke the copy constructor of the object that needs to be copied, but since it's the very same class, how would you handle this properly?

That's not necessarily a problem. If the chain of pointers ends somewhere, you'll just end up recursively copying everything on that chain; although it might be better to write a loop to avoid indefinite recursion. If it doesn't end, then it must be cyclic, so you'd need to check whether you get back to the object you started with.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
1

Normally you would just invoke the copy constructor of the object that needs to be copied, but since it's the very same class, how would you handle this properly?

In the same recursive manner as you call the destructor of the same class. Since your destructor assumes the ownership of the pointed object, you must do a deep copy.

Moving is simple, just clear the pointer so it doesn't get deleted when the moved from object is destroyed. Just like any other owned raw pointer.

C(const C& other): a(other.a), b(other.b), c(other.c) {
    if(other.c)
        this->c = new C(*other.c);
}
C(C&& other): a(other.a), b(other.b), c(other.c) {
    other.c = nullptr;
}

Remember that the stack size will limit the recursion of your data structure. If you would leave memory management outside the class, then you could iterate the linked objects with a loop instead of recursion.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 1
    There was a comment about this, but if you go with this solution _please do_ initialize `c` to `nullptr` in all non-copying constructors! – Jonathan H May 20 '15 at 14:46
  • 1
    @Sh3ljohn good point. I assumed that the initialization was there but left out of example code for simplicity. – eerorika May 20 '15 at 14:47
1

OK, the purpose of the question is unclear, and the way you'd write the copy and move operators would depend on the behaviour you wanted:

  • do you want ownership of the subordinate C moved to the new class on move?

  • do you want the subordinate C copied in the case of a copy? (deep copy)

  • on copy, should the destination C share the subordinate C with the source C?

We could take a guess and assume that the answers are: yes, yes, no:

class C
{
public:

    // custom destructor == rule of 5

    C() {}

    C(C&& r) noexcept
    : _child { r._child }
    {
        r._child = nullptr;
    }

    C& operator=(C&& r) noexcept {
        swap(r);
        return *this;
    }

    C(const C& r)
    : _child { r._child ? r._child->clone() : nullptr }
    {
    }

    C& operator=(const C& r) {
        C tmp { r };
        swap(tmp);
        return *this;
    }

    // test for null is not necessary
    virtual ~C() noexcept { delete _child; }

public:
    void swap(C& r) noexcept {
        using std::swap;
        swap(_child, r._child);
    }

    // in case C is a polymorphic base class
    virtual C* clone() const {
        return new C { *this };
    }

private:
    C* _child = nullptr;
};
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
0

This is one of the reasons why smart pointers were invented. They are self managed, so the only thing you need to do is set them when you want, and they take care of releasing the memory when it is no longer used.

#include <memory>

class C
{
public:
  C() {}

protected:
  int a;
  float b;
  std::shared_ptr<C> c;
}

PS: I didn't ask, but cases in which you need a pointer to self are not that common; are you sure there is no workaround in your case?

Jonathan H
  • 7,591
  • 5
  • 47
  • 80
  • 3
    Smart pointers are pretty ungood for linked lists, which this *could* be (and appears to most likely be). Especially when the list could be circular. I'm downvoting this as bad advice. – Cheers and hth. - Alf May 20 '15 at 14:22
  • You are right, which I why I wrote my PS. The OP doesn't say anything about the application, so without further information this is a perfectly valid solution. I'm not sure how I feel about voting because of how it _could be_ used. Knives can be used to kill, they're still a perfectly good choice for cutting things, like paper, even though scissors might be better then. – Jonathan H May 20 '15 at 14:30
  • For a blanket unqualified recommendation it's important that it would fail or add unreasonable overhead in the most likely cases. When I wrote "bad" advice I meant "most likely directly harmful" advice, not that it's imperfect. – Cheers and hth. - Alf May 20 '15 at 14:36
  • @Cheersandhth.-Alf No worries, in any case you're right in saying that the OP is unclear :) – Jonathan H May 20 '15 at 14:37