Why does copy constructor not need to check whether the input object pointers to itself or not?

Viewed 76

As the code below, the copy assignment operator has to check whether the input object pointers to itself or not. I wonder why copy constructor does not need to do the same check.

I am novice in C++.I would be grateful to have some help on this question.

  class rule_of_three
    {
        char* cstring; // raw pointer used as a handle to a dynamically-allocated memory block

        void init(const char* s)
        {
            std::size_t n = std::strlen(s) + 1;
            cstring = new char[n];
            std::memcpy(cstring, s, n); // populate
        }
     public:
        rule_of_three(const char* s = "") { init(s); }

        ~rule_of_three()
        {
            delete[] cstring;  // deallocate
        }

        rule_of_three(const rule_of_three& other) // copy constructor
        { 
            init(other.cstring);
        }

        rule_of_three& operator=(const rule_of_three& other) // copy assignment
        {
            if(this != &other) {
                delete[] cstring;  // deallocate
                init(other.cstring);
            }
            return *this;
        }
    };
2 Answers

Self-assignment sometimes happens, it's a part of a normal use of a class.

Passing a not-yet-constructed object as a parameter to its own copy (or move) constructor is not normal. While not undefined behavior per se1, there are no good reasons to do it, and it normally doesn't happen. It can happen accidentally, or if someone is deliberately trying to break your class.

Because of that, traditionally copy (and move) constructors don't check for &other != this.

But nothing stops you from doing it, if you want some extra safety:

rule_of_three(const rule_of_three& other) // copy constructor
{ 
    assert(&other != this);
    init(other.cstring);
}

1 [basic.life]/7 seems to allow that, as long as you don't access the not-yet-constructed object itself. Taking an address of it using & is allowed.

Let's suppose you have simple objects like Stormtroopers:

class Stormtrooper
    {
        char* cstring; // raw pointer used as a handle to a dynamically-allocated memory block

        void clone(const char* s)
        {
            std::size_t n = std::strlen(s) + 1;
            cstring = new char[n];
            std::memcpy(cstring, s, n); // populate
        }
     public:
        Stormtrooper(const char* s = "I am a Stormtrooper clone") { clone(s); }

        ~Stormtrooper()
        {
            delete[] cstring;  // deallocate
        }

        Stormtrooper(const Stormtrooper& other) // copy constructor
        { 
            clone(other.cstring);
        }

        Stormtrooper& operator=(const Stormtrooper& other) // copy assignment
        {
            if(this != &other) {
                delete[] cstring;  // deallocate
                clone(other.cstring);
            }
            return *this;
        }
    };

If you want to assign une Stormtrooper to another it is useful to check if the two Stormtroopers are already identical or not (and they usually are). In this way you avoid the clone() operation, because the Stormtroopers are already identical.

But if you want to create a new Stormtrooper, and you want him to be identical to another Stormtrooper (as usual) you can copy-construct it and in this case the clone() operation will be performed correctly.

In this way you can create an entire army of Stormtroopers quite easily.

Related