Subclassing, assignment operator and copy constructor - c++

I have a bunch of questions related to this, I couldn't find exact answers.
Class A is the main class, and B is a subclass
If A::operator=(const A & other) was defined, does the default implementation of B::operator= copies members of B then calls A::operator= ?
If A copy constructor was defined, does the default implementation of the copy constructor for B copy-constructs members of B, then calls A copy constructor?
Do I need to define above functions virtual in A to get this behavior? (I suppose yes for operator= and no for the copy constructor, as virtual constructors is a nonsense?)
Can I forbid overloading the assignment operator or the copy constructor for subclasses of A, to force the use of default implementations?
The idea behind this, being to offer a plugin API for my users. I need the API to be C++ as scripts are too slow (I'll try JIT compilation one day), but it should be very simple.

If the default copy-assignment operator of class B is not deleted, [class.copy]/28
The implicitly-defined copy/move assignment operator for a non-union class X performs memberwise copy-/move assignment of its subobjects. The direct base classes of X are assigned first, in the order of their declaration in the base-specifier-list [i.e. in the order in which they're listed after class X : /*here*/ {/*...*/};], and then the immediate non-static data members of X are assigned, in the order in which they were declared in the class definition.
Similarly, [class.copy]/15
The implicitly-defined copy/move constructor for a non-union class X performs a memberwise copy/move of its bases and members.
The order is: first the base class(es) (base class subobjects), then the direct non-static data members, similarly to 1).
For the behaviour described in 1) and 2), no. Virtual assignment operators are hardly ever useful. Constructors may not be virtual at all (doesn't make sense).
In order for a virtual function in a derived class B to override a virtual function in a base class A, it must have the same parameter types. That is, you could have a virtual A& operator=(A const&); in the base class A, but the override in class B had to look like virtual B& operator=(A const&);, which is not a copy-assignment operator for B, because of the parameter type.
Not without "hacks". And you're not actually overloading it, but hiding all base class assignment operators. Otherwise, this would be legal:
class A {};
class B { int i; };
A a;
B b = a; // using A::operator=(A const&)

Related

C++ primer 5 ed. Virtual destructor and move semantics

In C++ primer 5 ed. it is said:
"The fact that a base class needs a virtual destructor has an important indirect impact on the definition of base and derived classes: If a class defines a destructor—even if it uses = default to use the synthesized version—the compiler will not synthesize a move operator for that class (§13.6.2, p. 537)."
So for more understanding I've tried this:
class A
{
public:
A(){cout << "A::A()\n";}
virtual ~A(){cout << "A::~A()\n";}
A& operator = (const A&) {cout << "A::=\n"; return *this;};
A& operator = (A&&) = default;
};
int main()
{
A a;
A a2;
a2 = std::move(a); // why copy-assignment operator is not called here. Normally a will be "moved" through copy-ctor (moving through working copy-ctor)
std::cout << std::endl;
}
So according to the text above in the book class A has a virtual destructor thus the compiler won't synthesize a move-ctor/assignment operations But in main I've tried to move an object of type class A so normally move here uses copy-assignment instead of move-assignment but copy-assignment is not called here?!
So when a vairtual dtor causes a default operation be not synthesized? Thank you!
Your textbook is right, the compiler won't automatically synthesize a move operator. In the code sample you force the compiler to generate one by setting it to default, this can only fail if the move operator is implicitly deleted. Which is something else than it being implicitly declared.
Criteria for implicit deletion:
A has a non-static data member that is const
A has a non-static data member of a reference type
A has a non-static data member that cannot be move-assigned (has deleted, inaccessible, or ambiguous move assignment operator)
A has direct or virtual base class that cannot be move-assigned (has deleted, inaccessible, or ambiguous move assignment operator)
A has a non-static data member or a direct or virtual base without a move assignment operator that is not trivially copyable
A has a direct or indirect virtual base class
Criteria for implicit declaration:
there are no user-declared copy constructors;
there are no user-declared move constructors;
there are no user-declared copy assignment operators;
there are no user-declared destructors;
the implicitly-declared move assignment operator would not be defined as deleted
source

Using copy assignment in derived class

The cppreference says:
Because the copy assignment operator is always declared for any class, the base class assignment operator is always hidden. If a using-declaration is used to bring in the assignment operator from the base class, and its argument type could be the same as the argument type of the implicit assignment operator of the derived class, the using-declaration is also hidden by the implicit declaration.
From my understanding, the following code should not compile. Because
B::operator=(const B&) is implicitly declared.
both A::operator=(const A&) and using-declaration are hidden.
#include <iostream>
using namespace std;
class A {
public:
A& operator=(const A& A) {
cout << "A::opreator=" << endl;
}
};
class B : A {
public:
using A::operator=;
};
int main() {
A a1;
B b1;
b1 = a1;
}
However, it compiles successfully and prints "A::operator=", why?
From C++11 Standards#12.8 [emphasis added]:
24 Because a copy/move assignment operator is implicitly declared for a class if not declared by the user, a base class copy/move assignment operator is always hidden by the corresponding assignment operator of a derived class (13.5.3). A using-declaration(7.3.3) that brings in from a base class an assignment operator with a parameter type that could be that of a copy/move assignment operator for the derived class is not considered an explicit declaration of such an operator and does not suppress the implicit declaration of the derived class operator; the operator introduced by the using-declaration is hidden by the implicitly-declared operator in the derived class.
The implicit declaration of class B assignment operation will be like this:
B& B::operator=(const B&)
The parameter type of using-declaration assignment operator in class B is different from implicitly declared assignment operator. Hence, it suppress the implicit declaration of the derived class B operator.
For understanding on 1 & 2 w.r.t. to the code you have posted:
No, the implicit declaration of assignment operator is suppressed in class B.
No, they will not be hidden.
You can't hide the copy assignment operator of B because both operators you've mentioned take different parameters.
I think the reference you mentioned should be broken into 2 parts that suite your 2 question:
Yes, the copy assignments for base class (A) and derived class(B) are implicitly declared by default. You just overriden the implicit declaration on class A by output a message to stream.
the second part of the reference means that if the instance of class B is assigned to an instance of class A and there is a using-declaration to use class A assignment, then the assignment of class A will be used. Which means, if an instance of class B is assigned to an other instance of class B, the default implicit assignment will be used. Therefore, nothing is hidden due to the difference in argument type and the code will call the using-declaration (a.k.a the base class assignment) => the message is output to stream.
I don't see any conflict in this code with standard.
b1 = a1;
This assignment is done because you used using-declaration of base class.
and also " implicit assignment operator of the derived class" is provided by compiler because if you want to assign two objects of derived class it's possible.

Move constructor disappears in derived class when adding custom destructor

I have a move-only Base class and a Derived which inherits Base's constructors. I would like to give a Derived a custom destructor, but when I do so it no longer inherits Base's move constructor. Very mysterious. What is happening?
godbolt
// move-only
struct Base {
Base() = default;
Base(Base const &) = delete;
Base(Base &&) {}
};
struct Derived : public Base {
using Base::Base;
// remove this and it all works
~Derived() { /* ... */ }
};
int main() {
Base b;
// works
Base b2 = std::move(b);
Derived d;
// fails
Derived d2 = std::move(d);
}
The move constructor is not inherited with using Base::Base; in the way that you seem to think it is, because the move constructor in Base does not have the signature that a move constructor in Derived would have. The former takes a Base&&, the latter a Derived&&.
Then in Derived you are declaring a destructor. This inhibits the implicit declaration of a move constructor for Derived. So there is no move constructor in Derived.
The compiler then falls back to Derived's implicitly generated copy constructor for Derived d2 = std::move(d);. But that is defined as deleted because the base class of Derived is not copy-able. (You manually deleted Bases copy constructor.)
In overload resolution the deleted copy constructor is chosen over the Base classes inherited Base(Base&&) constructor (although a Derived rvalue could bind to Base&&), because the latter requires a conversion sequence that is not considered exact match, while binding to a const Derived& is considered exact match for the purpose of overload resolution.
Also there is the proposed wording for the resolution of CWG issue 2356 which would exclude the inherited Base move constructor from participating in overload resolution at all. (From what I can tell this is what the compiler are implementing already.)
If you don't have a good reason to declare a destructor, don't do so. If you do have a reason, then you need to default the move operations again, as you did for the move constructor in Base. (You probably want to default the move assignment operator as well if the classes are supposed to be assignable.)
If you intend to use the class hierarchy polymorphically, you should declare a virtual (defaulted) destructor in the polymorphic base, but you do not need to declare a destructor in the derived classes.
Move constructors are generated under specific circumstances.
https://en.wikipedia.org/wiki/Special_member_functions
In creating a destructor, you have stopped the generation of a move constructor by the compiler.
Also, create a virtual Base destructor if you don't have one. Default it if it doesn't have to do anything special. Same with your Base move constructor, just don't leave it empty, declare it default. You're using =delete, use =default as well.
The inherited move constructor does not have the signature for the derived class.
In the first case without the explicitly declared destructor the compiler implicitly declares the default move constructor for the derived class.
In the second case when the destructor is explicitly declared the move constructor is not implicitly declared by the compiler.
From the C++ 17 Standard (15.8.1 Copy/move constructors)
8 If the definition of a class X does not explicitly declare a move
constructor, a non-explicit one will be implicitly declared as
defaulted if and only if
(8.1) X does not have a user-declared copy constructor,
(8.2) X does not have a user-declared copy assignment operator,
—(8.3) X does not have a user-declared move assignment operator, and
> —(8.4) X does not have a user-declared destructor.
But in any case the ,move constructor of the base class is not the move constructor of the derived class due to different signatures.

Is a class with deleted copy-constructor trivially copyable?

Is this class:
class A {
public:
A() = default;
A(const A&) = delete;
};
trivially copyable? (At least clang seems to think so (live))
In particular, would
A a,b;
std::memcpy(&a, &b, sizeof(A));
invoke undefined behavior?
Context: This answer [deleted because proven wrong] plus its comment tree.
Update: The proposed resolution of CWG 1734, currently in "ready" status, would modify [class]/p6 to read:
A trivially copyable class is a class:
where each copy constructor, move constructor, copy assignment operator, and move assignment operator (12.8 [class.copy], 13.5.3
[over.ass]) is either deleted or trivial,
that has at least one non-deleted copy constructor, move constructor, copy assignment operator, or move assignment operator,
and
that has a trivial, non-deleted destructor (12.4 [class.dtor]).
This renders classes like
struct B {
B() = default;
B(const B&) = delete;
B& operator=(const B&) = delete;
};
no longer trivially copyable. (Classes of this sort include synchronization primitives like std::atomic<T> and std::mutex.)
However, the A in the OP has a implicitly declared, non-deleted copy assignment operator that is trivial, so it remains trivially copyable.
The original answer for the pre-CWG1734 situation is preserved below for reference.
Yes, somewhat counterintuitively, it is trivially copyable. [class]/p6:
A trivially copyable class is a class that:
has no non-trivial copy constructors (12.8),
has no non-trivial move constructors (12.8),
has no non-trivial copy assignment operators (13.5.3, 12.8),
has no non-trivial move assignment operators (13.5.3, 12.8), and
has a trivial destructor (12.4).
[class.copy]/p12:
A copy/move constructor for class X is trivial if it is not
user-provided, its parameter-type-list is equivalent to the
parameter-type-list of an implicit declaration, and if
class X has no virtual functions (10.3) and no virtual base classes (10.1), and
class X has no non-static data members of volatile-qualified type, and
the constructor selected to copy/move each direct base class subobject is trivial, and
for each non-static data member of X that is of class type (or array thereof), the constructor selected to copy/move that member is
trivial;
Similarly ([class.copy]/p25):
A copy/move assignment operator for class X is trivial if it is not
user-provided, its parameter-type-list is equivalent to the
parameter-type-list of an implicit declaration, and if
class X has no virtual functions (10.3) and no virtual base classes (10.1), and
class X has no non-static data members of volatile-qualified type, and
the assignment operator selected to copy/move each direct base class subobject is trivial, and
for each non-static data member of X that is of class type (or array thereof), the assignment operator selected to copy/move that member is
trivial;
[class.dtor]/p5:
A destructor is trivial if it is not user-provided and if:
the destructor is not virtual,
all of the direct base classes of its class have trivial destructors, and
for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial
destructor.
[dcl.fct.def.default]/p5:
A function is user-provided if it is user-declared and not explicitly
defaulted or deleted on its first declaration.
Indeed, this has been a source of problems for the committee itself, because under the current definition atomic<T> (along with mutexes and condition variables) would be trivially copyable. (And obviously, allowing someone to memcpy over an atomic or a mutex without invoking UB would be ... let's just say seriously problematic.) See also N4460.

Do derived classes indirectly inherit base's assignment operator?

I'm trying to understand this behaviour but it seems I don't. Please see this code:
#include <iostream>
using namespace std;
class Base
{
public:
void operator=(const Base& rf)
{
cout << "base operator=" << endl;
this->y = rf.y;
}
int y;
Base() : y(100) { }
};
class Derived : public Base
{
public:
int x;
Derived() : x(100) { }
};
int main()
{
Derived test;
Derived test2;
test2.x = 0;
test2.y = 0;
test.operator=(test2); // operator auto-generated for derived class but...
cout << test.x << endl << test.y << endl;
cin.ignore();
return 0;
}
PROGRAM OUTPUT:
> base operator=
> 0
> 0
Now where I'm confused is:
The rule says that a derived class never inherits the assigment operator, instead it creates its own operator= however in this example base's operator= gets invoked on the derived class.
Second I was able to explicitly invoke an assigment operator on a derived class, which isn't in turn explicitly defined in the derived class.
Now if I understand it correctly, this means that any user defined base's operator always gets invoked on the derived class?
The generated ones automatically call the base-class assignment operator.
// generated version looks basically like this
Derived& operator=(Derived const& other){
Base::operator=(static_cast<Base const&>(other));
x = other.x;
return *this;
}
The cast is there to avoid an accidential call to a templated Base::operator= like this one:
template<class Other>
Base& operator=(Other const& other); // accepts everything
Or a strange one like this:
// forward-declare 'Derived' outside of 'Base'
Base& operator=(Derived const& other); // accepts derived class (for whatever reason)
Second I was able to explicitly invoke an assigment operator on a derived class, which isn't in turn explicitly defined in the derived class.
The compiler automatically declares an assignment operator if you do not and your class allows it (i.e., no reference members and some other arcane rules) and additionally defines it if you actually use it somewhere.
The compiler-generated assignment operator calls the assignment operator of each subobject. That includes base classes and non-static member variables.
The standard says (section 12.8 [class.copy]):
If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy assignment operator is defined as deleted; otherwise, it is defined as defaulted (8.4). The latter case is deprecated if the class has a user-declared copy constructor or a user-declared destructor. The implicitly-declared copy assignment operator for a class X will have the form
X& X::operator=(const X&)
if
each direct base class B of X has a copy assignment operator whose parameter is of type const B&,
const volatile B& or B, and
for all the non-static data members of X that are of a class type M (or array thereof), each such class type has a copy assignment operator whose parameter is of type const M&, const volatile M& or M.
Otherwise, the implicitly-declared copy assignment operator will have the form
X& X::operator=(X&)
and
The implicitly-defined copy/move assignment operator for a non-union class X performs memberwise copy/move assignment of its subobjects. The direct base classes of X are assigned first, in the order of their declaration in the base-specifier-list, and then the immediate non-static data members of X are assigned, in the order in which they were declared in the class definition. Let x be either the parameter of the function
or, for the move operator, an xvalue referring to the parameter. Each subobject is assigned in the manner appropriate to its type:
if the subobject is of class type, as if by a call to operator= with the subobject as the object expression and the corresponding subobject of x as a single function argument (as if by explicit qualification; that is, ignoring any possible virtual overriding functions in more derived classes);
if the subobject is an array, each element is assigned, in the manner appropriate to the element type;
if the subobject is of scalar type, the built-in assignment operator is used.
It is unspecified whether subobjects representing virtual base classes are assigned more than once by the implicitly-defined copy assignment operator.
That's because the implicitly defined operator = calls the base classes operator =. See the FAQ lite:
I'm creating a derived class; should my assignment operator call my base class's assignment operator?
Yes (if you need to define an assignment operator in the first place).
If you define your own assignment operator, the compiler will not automatically call your base class's assignment operator for you. Unless your base class's assignment operator itself is broken, you should call it explicitly from your derived class's assignment operator (again, assuming you create one in the first place).
However if you do not create your own assignment operator, the one that the compiler creates for you will automatically call your base class's assignment operator.
4 things never get inherited
Constructor
Copy-constructor
Assignment operator
Destructor
Even you have not written the assignment operator your code will be Woking fine.
Whenever you use assignment operator compiler will make sure that assignment operator for each member object and base class get called.