I have a base class, and I do not want to make derived class copyable. In order to make everything explicit I implement it in that way:
class A {
public:
A() = default;
virtual ~A() = default;
A(const A&) = delete;
A(const A&&) = delete;
A& operator=(const A&) = delete;
A& operator=(const A&&) = delete;
virtual void vFun() = 0;
};
class B : public A {
public:
B() = default;
virtual ~B() = default;
B(const B&) = delete;
B(const B&&) = delete;
B& operator=(const B&) = delete;
B& operator=(const B&&) = delete;
virtual void vFun() override {}
};
Is this correct way of doing such things? According to my knowledge and what I have read, the answer is yes, but I would like to be sure before I introduce it into production system.
EDIT
Taking things into conclusion:
1) Almost always move operators should not be deleted. That's because "there's an infinity of things that require movability".
2) For abstract base class, it's safer to allow compiler to generate special member function, and move deletion into derived class if such necessity exists.
Nope, this is completely incorrect.
Firstly, in your quest to make the derived class noncopyable, you have made it non-movable, which renders it nearly useless.
Secondly, there's no reason for A to be noncopyable at all. Each derived class can just make itself noncopyable if it wants to. A is already abstract and cannot be sliced so there's no reason to make A noncopyable.
Related
Assume I want to suppress copying/moving in a base class, but allow it for a derived class. I can accomplish the functionality like this:
class Base {
public:
virtual ~Base() = default;
virtual bool magic() const;
protected:
Base() = default;
Base(const Base&) = default;
Base& operator=(const Base&) = default;
Base(Base&&) = default;
Base& operator=(Base&&) = default;
};
class Derived : public Base {
public:
Derived() = default;
Derived(int x, double y) : x_(x), y_(y) {};
bool magic() const;
private:
int x_;
double y_;
}
The "problem" is that this doesn't follow C.67 in the ISO C++ Core Guidelines:
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-copy-virtual
C.67 states
C.67: A polymorphic class should suppress public copy/move
Reason A polymorphic class is a class that defines or inherits at least one virtual function. It is likely that it will be used as a base class for other derived classes with polymorphic behavior. If it is accidentally passed by value, with the implicitly generated copy constructor and assignment, we risk slicing: only the base portion of a derived object will be copied, and the polymorphic behavior will be corrupted.
If the class has no data, =delete the copy/move functions. Otherwise, make them protected.
The way I interpret this is that I should delete my copy/move constructors/assignment operators in the base class, since it has no data members:
class Base {
public:
Base(const Base&) = delete;
Base& operator=(const Base&) = delete;
Base(Base&&) = delete;
Base& operator=(Base&&) = delete;
virtual ~Base() = default;
virtual bool magic() const;
protected:
Base() = default;
};
class Derived : public Base {
public:
Derived() = default;
Derived(int x, double y) : x_(x), y_(y) {};
Derived(const Derived& d) : Base(), x_(d.x_), y_(d.y_) {};
...
bool magic() const;
private:
int x_;
double y_;
}
But doing so, will force me to implement copy/move constructors/assignment operators in my derived class (which is a point with the guideline, I assume). I can come up with no other way than to manually copy all the data members of my derived class, which seems quite bloated. If I add a new data member and forget to update my copy/move functions, they will break.
Is there an easier way to define copy/move constructors/assignment operators in my derived class, when they are deleted in the base class? And what is the best practice in this situation?
EDIT:
As pointed out in the comments, the copy/move constructors/assignment operators should not be public in the derived class either, according to the same guideline. But the problem remains when defining them as protected.
A user-declared dtor prevents the autogeneration of the move-ctor/-assignment-operator,
but will the autogeneration only be prevented in the class where the dtor has been defined or will the autogeneration be prevented in all derived classes too? I am asking this, because I am using many pure virtual classes which all provide a user-declared dtor. Do I have to upgrade now all this classes to get move-support or will it work still out-of-the-box?
Here is an example of how my scenarios currently looks like:
struct BigData {};
struct BaseA
{
virtual void func() = 0;
virtual ~BaseA() = default;
};
struct A : public BaseA
{
BigData _data;
void func() override {}
};
Now, which of the following variants I have to use to
be sure, that moving will be used like in the following
example?
A a;
std::vector< A > va;
va.push_back( std::move( a ) ); //Should really use move instead of copy
Variant 1: Upgrade base class only
struct BaseA
{
virtual void func() = 0;
virtual ~BaseA() = default;
BaseA() = default;
BaseA(BaseA&&) = default;
BaseA& operator=(BaseA&&) = default;
BaseA(const BaseA&) = default;
BaseA& operator=(const BaseA&) = default;
};
struct A : public BaseA
{
BigData _data;
void func() override {}
};
Variant 2: Upgrade derived class only
struct BaseA
{
virtual void func() = 0;
virtual ~BaseA() = default;
};
struct A : public BaseA
{
BigData _data;
void func() override {}
A() = default;
A(A&&) = default;
A& operator=(A&&) = default;
A(const A&) = default;
A& operator=(const A&) = default;
};
Variant 3: Upgrade base class and derived class
struct BaseA
{
virtual void func() = 0;
virtual ~BaseA() = default;
BaseA() = default;
BaseA(BaseA&&) = default;
BaseA& operator=(BaseA&&) = default;
BaseA(const BaseA&) = default;
BaseA& operator=(const BaseA&) = default;
};
struct A : public BaseA
{
BigData _data;
void func() override {}
A() = default;
A(A&&) = default;
A& operator=(A&&) = default;
A(const A&) = default;
A& operator=(const A&) = default;
};
Variant 4: Nothing to do
Not having a move constructor in the base vs. having a deleted move constructor in the base are two different things.
For the first, the derived classes can still go with the rule of zero and have a default generated move constructor created by the compiler.
For the latter, the default move would be also deleted in the derived.
When you have a user defined destructor in your class (or a user defined copy constructor, or a user defined copy assignment operator) the default move operations are not provided, but they are not implicitly deleted. Thus the derived class is still entitled for the default move operations if it follows the rules for having them, without the need to explicitly declare them as =default.
Cpp Reference says:
The implicitly-declared or defaulted move constructor for class T is defined as deleted if any of the following is true:
...
T has direct or virtual base class that cannot be moved (has deleted, inaccessible, or ambiguous move constructors);
...
Note that having a user declared destructor doesn't make a class un-moveable, it just doesn't have a default generated move.
There is well-known clone idiom for copying Derived objects via pointer to Base class.
class Base{
int b;
public:
virtual unique_ptr<Base> clone() const = 0;
virtual ~Base() = default;
};
class Derived : public Base {
int d;
public:
virtual unique_ptr<Base> clone() const override {
return std::make_unique<Derived>(*this);
}
}
However, I can't find clear instructions how to define copy constructors and assignments in this case. This is how I suppose it should be done in Base class:
class Base {
protected:
Base(const Base&) = default;
private:
Base& operator=(const Base&) = delete;
}
Is it necessary (in order to avoid potential slices)? Is it right way to do it? Does it suffice or should I add such declarations to Derived class as well?
As derived classes use the copy constructor to create clones, you may like to make the copy constructors non-public to avoid accidental slicing but accessible to derived classes.
protected fills this requirement. It needs to be applied to each class' copy constructor because the compiler-generated copy constructor is public. It also makes sense to apply the same treatment to the assignment operator.
That also prevents std::make_unique from accessing the copy constructor though:
class A
{
protected:
A(A const&) = default;
A& operator=(A const&) = default;
public:
A();
virtual std::unique_ptr<A> clone() const = 0;
};
class B : public A
{
protected:
B(B const&) = default;
B& operator=(B const&) = default;
public:
B();
std::unique_ptr<A> clone() const override {
return std::unique_ptr<A>(new B(*this));
}
};
Deleting the copy assignment operator is probably a good idea, unless you need it.
Deleting operator=(const Base&) in Base is enough, as the implicitly declared copy assignment operator is defined as deleted for a derived class if the base class has no copy assignment operator (see cppreference.com).
If you really want copy assignment you can make the copy assignment operator virtual, and carefully implement the correct behaviour in the derived classes by
calling Base::operator= to assign the Base class members, and
assigning the members of the derived class, using dynamic_cast to ensure that the argument is of the correct type .
If done correctly, this avoids object slicing and retains the correct type.
An example (with copy constructor details omitted):
struct Point {
virtual Point& operator=(const Point& p) =default;
int x;
int y;
};
struct Point3d :public Point{
virtual Point3d& operator=(const Point& p);
int z;
};
Point3d& Point3d::operator=(const Point& p)
{
Point::operator=(p);
auto p3d = dynamic_cast<const Point3d*>(&p);
if(p3d){
z = p3d->z;
} else {
z = 0;
}
return *this;
}
According to CppCoreGuideline, I should disable the copy constructor of a base class and propose a clone method: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rc-copy-virtual
For example:
class B {
public:
explicit B() = default;
B(B&&) = default; // Move constructor
B& operator=(B&&) = default; // Move assignment operator
B(const B&) = delete; // Copy constructor
B& operator=(const B&) = delete; // Copy assignment
virtual ~B() = default;
virtual unique_ptr<B> clone()
{
return unique_ptr<B>{new B{*this}}; // how do this without copy constructor ?
}
private:
int c;
int d;
};
class D : public B {
public:
explicit D() = default;
D(D&&) = default; // Move constructor
D& operator=(D&&) = default; // Move assignment operator
D(const B&) = delete; // Copy constructor
D& operator=(const D&) = delete; // Copy assignment
virtual ~D() = default;
virtual unique_ptr<B> clone() override
{
// how can I copy all private data member of base class ???
}
};
but how can I copy all private data member in clone method? Obviously I'll use the CRTP pattern : C++: Deep copying a Base class pointer
I think the simplest way is to actually make the special members protected instead of deleted. This still prevents slicing, but makes it easier to implement clone(). Note that both the copy and move members need to be treated this way.
class B {
public:
// if this is truly intended to be a polymorphic base class, it probably
// doesn't make sense for the base to be able to clone itself.
virtual unique_ptr<B> clone() = 0;
protected:
B(B const& ) = default;
B& operator=(B const& ) = default;
private:
int c;
int d;
};
Which also allows the derived classes to do this easily:
class D : public B {
public:
D(D const& ) = default; // this is safe now
D& operator=(D const& ) = default;
unique_ptr<B> clone() override {
return unique_ptr<D>(new D(*this));
}
// ...
};
Rather than disabling the copy constructor, consider marking it protected. That way, clients of the class can't accidentally create a copy, but instances of the class can invoke the copy constructor as needed to implement the clone function. You can use the defaulted version of the copy constructor assuming you aren't doing any explicit resource management. Then, to implement clone, you can do something like this:
virtual unique_ptr<B> clone() override
{
return make_unique<D>(*this);
}
This invokes the object's own (protected) copy constructor, which in turn will invoke the base's (protected) copy constructor, etc.
As a note, there's no need to use CRTP here. Using good old fashioned copy constructors should be all you need.
To disable copy constructor and assignment operator, it is clear that we could do either, since c++11:
class A {
public:
A(const A&) = delete;
A& operator=(const A&) = delete;
}
or for c++03:
class A {
private:
A(const A&);
A& operator=(const A&);
}
however, what happens with this:
class A {
private:
A(const A&) = delete;
A& operator=(const A&) = delete;
}
i guess this also leads to the same result. Is there any side effect?
It doesn't matter what access you give a deleted function - it simply doesn't exist(¹), so it is inaccessible whatever the caller.
The error messages may be slightly more confusing. See for example http:://cpp.sh/9hv7y where the first error is about "private" rather than "deleted".
¹ "it doesn't exist" is a simplification. It exists in the sense that it participates in overload resolution, but it is an error if it is the selected function. Thus
struct only_double {
only_double(intmax_t) = delete;
only_double(double arg);
};
only_double zero(0); // Error - deleted constructor called