Andrew Voelkel
Andrew Voelkel

Reputation: 589

copy constructors and assignment operators in templates with inheritance

template<typename T = uint8_t> class ArrayRef {
  using uint = unsigned int;
protected:
  ArrayRef() {}
  ArrayRef(const ArrayRef&) {}
  ArrayRef& operator=(const ArrayRef& other) { return *this; }
};

class ByteArray : ArrayRef<uint8_t> {
  ByteArray(const ArrayRef&);
  ByteArray& operator=(const ArrayRef&);
public:
  ByteArray() {}
};

class Base {
  using uint = unsigned int;
protected:
  Base() {}
  Base(const Base&) {}
  Base& operator=(const Base& other) { return *this; }
};

class Derived : Base {
  Derived(const Derived&);
  Derived& operator=(const Derived& other) { return *this; }
public:
  Derived() {}
};

int main() {
  ByteArray ba;
  ByteArray ba2 = ba;  // no error, why?
  ba = ba2;            // no error why?
  Derived d;
  Derived d2 = d;      // error (expected) - Calling a private constructor
  d = d2;              // error (expected) - Calling private assignment operator
}

Two questions about the code above.

Upvotes: 3

Views: 258

Answers (1)

Ted Lyngmo
Ted Lyngmo

Reputation: 117433

  • Can you explain why the templated code behaves differently than the non-templated code? (see the comments in main().

The difference does not have anything to do with templates.

The difference is that your copy constructor and copy assignment operator are implicitly defined (as public) in ByteArray. In Derived you've made them private. If you take the template out of the question, it may be easier to see the difference between your two versions:

class ArrayRef {
protected:
    ArrayRef() {}
    ArrayRef(const ArrayRef&) {}
    ArrayRef& operator=(const ArrayRef& other) { return *this; }
};

class ByteArray : ArrayRef {
    ByteArray(const ArrayRef&);            // your converting ctor
    ByteArray& operator=(const ArrayRef&); // your converting assignment op
public:
    ByteArray() {}

    // copy contructor - implicitly defined:
    // ByteArray(const ByteArray&) = default;

    // copy assignment operator - implicitly defined:
    // ByteArray& operator=(const ByteArray&) = default;
};

If you now try copying a ByteArray it will work just as fine as when it was based on an instance of a class template. Compare the above ByteArray with your Derived in which you've actually made the copy constructor and copy assignment operator private.

  • How would I go about creating private copy constructors and assignment operators properly for templated code like this, in order to prevent object copies?

In order to prevent copies, you may delete them:

class ByteArray : private ArrayRef<uint8_t> {
public:
    ByteArray(const ByteArray&) = delete;
    ByteArray& operator=(const ByteArray&) = delete;
    ByteArray() {}
};

or make them private to allow ByteArrays and friends to make copies:

class ByteArray : private ArrayRef<uint8_t> {
private:
    ByteArray(const ByteArray&) { ... }; // or `= default`
    ByteArray& operator=(const ByteArray&) { ...; return *this; } // or `= default`
public:
    ByteArray() {}
};

Note that the private (converting) constructor and (converting) assignment operator that takes a const ArrayRef<uint8_t>& as input does not prevent the implicitly defined copy constructor and copy assignment operator that takes a const ByteArray& as input from behing created in ByteArray.

In your Derived class, you've actually made the copy constructor and copy assignment operator private which is why you get the expected compilation errors when trying to use those.

Here's a full example that includes your (converting) constructor and assignment operator as well as a (user defined) copy constructor and copy assignment operator.

#include <cstdint>
#include <iostream>

template<typename T = uint8_t> class ArrayRef {
protected:
    ArrayRef() {}
    ArrayRef(const ArrayRef&) {}
    ArrayRef& operator=(const ArrayRef& other) { return *this; }
};

class ByteArray : ArrayRef<uint8_t> {
    ByteArray(const ArrayRef&) { std::cout << "your converting ctor\n"; }
    ByteArray& operator=(const ArrayRef&) {
       std::cout << "your converting assignment op\n"; return *this;
    }
    
public:
    ByteArray(const ByteArray&) { std::cout << "copy ctor\n"; }
    ByteArray& operator=(const ByteArray&) {
        std::cout << "copy assignment op\n"; return *this;
    }
    ByteArray() {}
};

int main() {
    ByteArray a;
    ByteArray b = a;
    a = b;
}

You can see in the output that none of your private methods are used:

copy ctor
copy assignment op

Upvotes: 2

Related Questions