3

Suppose I have a silly class hierarchy.

class Base {
public:
    Base() = default;
    virtual ~Base() = default;
    virtual void print() = 0;
};

class Derived : public Base {
public:
    Derived() {
        stuff.resize(1000);
    }
    void print() override {
        cout << "Whatever\n";
    }
private:
    std::vector<int> stuff;
};

And I want to use it like this:

void printIt(Base* b) {
    b->print();
}

int main() {

    Derived d1;
    Derived d2 = d1;

    printIt(&d1);
    printIt(&d2);
}

This compiles and works fine, but Clang warns me:

warning: definition of implicit copy constructor for 'Base' is deprecated because it has a user-declared destructor

That seems fine and correct - if people copy Base bad things will happen so I'll delete the copy constructor.

class Base {
public:
    Base() = default;
    virtual ~Base() = default;
    virtual void print() = 0;

    Base(const Base&) = delete;
    Base& operator=(const Base&) = delete;
};

However then I get this error:

main.cpp:38:17: error: call to implicitly-deleted copy constructor of 'Derived'
        Derived d2 = d1;
                ^    ~~
main.cpp:18:21: note: copy constructor of 'Derived' is implicitly deleted because base class 'Base' has a deleted copy
      constructor
    class Derived : public Base {
                    ^
main.cpp:14:9: note: 'Base' has been explicitly marked deleted here
        Base(const Base&) = delete;
        ^

Ok... fair enough. What if I try to add it back in explicitly?

class Derived : public Base {
public:
    Derived() {
        stuff.resize(1000);
    }
    void print() override {
        cout << "Whatever\n";
    }

    Derived(const Derived&) = default;

private:
    std::vector<int> stuff;
};

Then:

main.cpp:40:17: error: call to implicitly-deleted copy constructor of 'Derived'
        Derived d2 = d1;
                ^    ~~
main.cpp:27:9: note: explicitly defaulted function was implicitly deleted here
        Derived(const Derived&) = default;
        ^
main.cpp:18:21: note: copy constructor of 'Derived' is implicitly deleted because base class 'Base' has a deleted copy
      constructor
    class Derived : public Base {
                    ^
main.cpp:14:9: note: 'Base' has been explicitly marked deleted here
        Base(const Base&) = delete;
        ^

No joy, which also kind of makes sense. What is the solution here? Should I just ignore the original warning? Maybe I should add Base(Base&) etc as protected?

Timmmm
  • 88,195
  • 71
  • 364
  • 509

2 Answers2

1

So it does compile without warnings if I make the copy/assignment protected. This seems like a reasonable solution:

class Base {
public:
    Base() = default;
    virtual ~Base() = default;
    virtual void print() = 0;

protected:        
    Base(const Base&) = default;
    Base& operator=(const Base&) = default;
};

class Derived : public Base {
public:
    Derived() {
        stuff.resize(1000);
    }
    void print() override {
        cout << "Whatever\n";
    }

private:
    std::vector<int> stuff;
};

void printIt(Base* b) {
    b->print();
}

int main() {

    Derived d1;
    Derived d2 = d1;

    printIt(&d1);
    printIt(&d2);
}
Timmmm
  • 88,195
  • 71
  • 364
  • 509
0

If you want to use it like Derived d2 = d1; you will need to do 3 things:

1) make Base copy (and move) constructors and assignment operators protected and not deleted to delegate capability to allow/prohibit copy/move to derived class

protected: Base(const Base&) = default;
protected: Base& operator=(const Base&) = default;

2) make Derived copy (and move) constructors and assignment operators public to indicate that copy/move is allowed

public: Derived(const Derived&) = default;
public: Derived& operator=(Derived&) = default;

3) mark Derived as final to prevent accidental slicing when someone decides to derive from it again

class Derived final: public Base
user7860670
  • 35,849
  • 4
  • 58
  • 84
  • It seems you don't actually need to declare `Derived`'s copy/assignment functions as public - I guess the implicitly generated ones are always public. And I guess by "splicing" you meant object slicing? Good point about final to prevent that. – Timmmm Jan 04 '18 at 11:28
  • Can you explain how does changing the constructors to protected is any different than in public? Both its children objects do know of the Base's constructor existence and 2 Base objects are created similarly in Public. –  Jan 04 '18 at 11:46
  • It means you can't do something like: `void foo(Base* a, Base* b) { *a = *b; }` ... I think. – Timmmm Jan 04 '18 at 11:49
  • @AlisherKassymov If constructor is `public` then anywhere in the program there could be some code performing `Base` to `Base` copy / move. Note that even though you define default `Base` constructor as `public` in reality is actually also `protected` because `Base` class is abstract. So you may want to change all the `Base` constructors to be `protected` for to make things more explicit. – user7860670 Jan 04 '18 at 11:55
  • @Timmmm There is indeed no need to do so, however it is always a good idea to make things explicit. – user7860670 Jan 04 '18 at 11:57
  • @VTT Ok, so if you make a parent class constructor protected, you can access it only if you create a Derived object am I right? So Protected is accessible only to its child class. Also, if you inherit the Base (Parent) in a protected way, there is no way to initiate a Base object of type Base, as only Derived has an access to it. –  Jan 04 '18 at 13:47