Rule A12-8-6 (required, implementation, automated)

Copy and move constructors and copy assignment and move assignment operators shall be declared protected or defined “=delete” in base class.

Rationale

Invoking copy or move constructor or copy assignment or move assignment operator from the top of a class hierarchy bypasses the underlying implementations. This results in “slicing” where only the base sub-objects being copied or moved.

Example

// $Id: A12-8-6.cpp 289436 2017-10-04 10:45:23Z michal.szczepankiewicz $
#include <memory>
#include <utility>
#include <vector>
class A // Abstract base class
{
public:
A() = default;
A(A const&) = default; // Non-compliant
A(A&&) = default;
// Non-compliant
virtual ~A() = 0;
A& operator=(A const&) = default; // Non-compliant

A& operator=(A&&) = default;
};
class B : public A
{
};
class C // Abstract base class
{
public:
C() = default;
virtual ~C() = 0;

// Non-compliant

protected:
C(C const&) = default;
// Compliant
C(C&&) = default;
// Compliant
C& operator=(C const&) = default; // Compliant
C& operator=(C&&) = default;
// Compliant
};
class D : public C
{
};
class E // Abstract base class
{
public:
E() = default;
virtual ~E() = 0;
E(E const&) = delete;
// Compliant
E(E&&) = delete;
// Compliant
E& operator=(E const&) = delete; // Compliant
E& operator=(E&&) = delete;
// Compliant
};
class F : public E
{
};
class G // Non-abstract base class
{
public:
G() = default;
virtual ~G() = default;
G(G const&) = default;
// Non-compliant
G(G&&) = default;
// Non-compliant
G& operator=(G const&) = default; // Non-compliant
G& operator=(G&&) = default;
// Non-compliant
};
class H : public G
{
};
void Fn1() noexcept
{
B obj1;
B obj2;
A* ptr1 = &obj1;

A* ptr2 = &obj2;
// Partial assignment only
*ptr1 = *ptr2;
*ptr1 = std::move(*ptr2); // Partial move only
D obj3;
D obj4;

C* ptr3 = &obj3;

C* ptr4 = &obj4;

//*ptr3 = *ptr4; // Compilation error - copy assignment operator of class C

// is protected

//*ptr3 = std::move(*ptr4); // Compilation error - move assignment operator

// of class C is protected
F obj5;
F obj6;

E* ptr5 = &obj5;

E* ptr6 = &obj6;

//*ptr5 = *ptr6; // Compilation error - use of deleted copy assignment

// operator

//*ptr5 = std::move(*ptr6); // Compilation error - use of deleted move

// assignment operator
H obj7;
H obj8;

G* ptr7 = &obj7;

G* ptr8 = &obj8;

// Partial assignment only
*ptr7 = *ptr8;
*ptr7 = std::move(*ptr8); // Partial move only
}
class I // Non-abstract base class
{
public:
I() = default;
~I() = default;

protected:
I(I const&) = default;
// Compliant
I(I&&) = default;
// Compliant
I& operator=(I const&) = default; // Compliant
I& operator=(I&&) = default;
// Compliant
};
class J : public I
{
public:
J() = default;
~J() = default;
J(J const&) = default;
J(J&&) = default;
J& operator=(J const&) = default;
J& operator=(J&&) = default;
};
void Fn2() noexcept
{
std::vector<I> v1;

// v1.push_back(J{}); // Compilation-error on calling a deleted move
// constructor of I class, slicing does not occur
// v1.push_back(I{}); // Compilation-error on calling a deleted move
// constructor of I class

std::vector<J> v2;
v2.push_back(J{}); // No compilation error

std::vector<std::unique_ptr<I>> v3;
v3.push_back(std::unique_ptr<I>{});
// No compilation error
v3.push_back(std::make_unique<I>()); // No compilation error
v3.push_back(std::make_unique<J>()); // No compilation error
v3.emplace_back();
// No compilation error

}

See also

MISRA C++ 2008 [7]: Rule 12-8-2 The copy assignment operator shall be declared protected or private in an abstract class. HIC++ v4.0 [9]: 12.5.8 Make the copy assignment operator of an abstract class protected or define it =delete. C++ Core Guidelines [11]: C.67: A base class should suppress copying, and provide a virtual clone instead if "‘copying"’ is desired. C++ Core Guidelines [11]: C.81: Use =delete when you want to disable default behavior (without wanting an alternative).