Abstract classes and move semantics - c++

According to “Rule Of Five” when I declare one of: copy or move operation or destructor I must write all of them, because the compiler doesn't generate them (some of them) for me. But if my class (A) derives from an abstract class with a virtual destructor, does this mean that the destructor in class A will be considered "user-defined"? As a consequence, will move semantics not work with objects of this class A because compiler won't generate a move constructor for me?
struct Interface {
virtual void myFunction() = 0;
virtual ~Interface() {};
};
struct A : public Interface {
void myFunction() override {};
};

In [class.copy]:
If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared
as defaulted if and only if
(9.1) — X does not have a user-declared copy constructor,
(9.2) — X does not have a user-declared copy assignment operator,
(9.3) — X does not have a user-declared move assignment operator, and
(9.4) — X does not have a user-declared destructor.
[ Note: When the move constructor is not implicitly declared or explicitly supplied, expressions that otherwise
would have invoked the move constructor may instead invoke a copy constructor. —end note ]
Interface does have a user-declared destructor, so Interface's move constructor will not be implicitly declared as defaulted. But A doesn't fit any of those bullet points - so it will have an implicit move constructor that is defaulted. A(A&& )will just copy the Interface part, as per the note. We can verify this by adding members to Interface and A:
#include <iostream>
template <char c>
struct Obj {
Obj() { std::cout << "default ctor" << c << "\n"; }
Obj(const Obj&) { std::cout << "copy ctor" << c << "\n"; }
Obj(Obj&&) { std::cout << "move ctor" << c << "\n"; }
};
struct Interface {
virtual void myFunction() = 0;
virtual ~Interface() {};
Obj<'I'> base;
};
struct A : public Interface {
void myFunction() override {};
Obj<'A'> derived;
};
int main() {
A a1; // default I, default A
std::cout << "---\n";
A a2(std::move(a1)); // copy I, move A
std::cout << "---\n";
A a3(a2); // copy I, copy A
}

Related

Defaulted destructor in base class disable move constructor in child class if there is a member

Why does defaulted (user declared) destructor in Base1 prevent generation of move constructor/operator in Child1 class, but everything work fine when I move member data from Base (Base2) to Child (Child2) class?
struct Data {
Data() {}
Data(Data&&) noexcept { cout << "Move constructor" << endl; }
Data& operator=(Data&&) noexcept {
cout << "Move assign" << endl;
return *this;
}
vector<int> vec;
};
struct Base1 {
virtual void fun() { cout << "Base1::fun" << endl; }
virtual ~Base1() = default;
Data data;
};
struct Child1 : public Base1 {
void fun() override { cout << "Child1::fun" << endl; }
};
struct Base2 {
virtual void fun() { cout << "Base2::fun" << endl; }
virtual ~Base2() = default;
};
struct Child2 : public Base2 {
void fun() override { cout << "Child2::fun" << endl; }
Data data;
};
int main() {
Child1 c1;
auto obj1 = std::move(c1); // error
Child2 c2;
auto obj2 = std::move(c2);
}
My current understanding is that when I declare destructor as “default” in Base (BaseDel), then move constructor should be “deleted” in Base (BaseDel) and in Child (ChildDel) class. Is this correct? Member location shouldn’t matter, I think. If I do that explicit, I get expected error:
struct BaseDel {
BaseDel() {}
virtual void fun() { cout << "BaseDel::fun" << endl; }
BaseDel(BaseDel&& st) = delete;
virtual ~BaseDel() = default;
};
struct ChildDel : public BaseDel {
ChildDel() {}
void fun() override { cout << "ChildDel::fun" << endl; }
Data data;
};
int main() {
ChildDel cd;
auto objd = std::move(cd); // OK, expected error
}
The implicit move constructor is not (only) deleted, it is not declared in the first place when you have a user-declared destructor, as is the case with Base1 and Base2.
Therefore the move constructor can never be considered in overload resolution and so auto obj1 = std::move(c1);, while it can call Child1's move constructor, needs to fall back to copy construction for the Base1 subobject.
The implicitly-declared copy constructors of both Base1 and Child1 are defined as deleted, because Data's implicitly-declared copy constructor is defined as deleted, because Data has a user-defined move constructor. Therefore auto obj1 = std::move(c1); will fail with an error that the implicitly-declared copy constructor is deleted.
For Base2 the copy constructor is not defined as deleted, because it doesn't have a Data member and so auto obj2 = std::move(c2); will call Child2's move constructor (which also uses Data's move constructor), but use the copy constructor for the Base2 subobject.

Explicitly defaulted destructor disables default move constructor in a class

I have run into a problem that a move constructor of a superclass did not get invoked properly when its subclass has an explicitly defaulted destructor. The move constructor does get invoked when the destructor is implicitly defaulted (not provided at all in the supclass definition).
I am aware of the constraints that the compilers should apply to default move constructors. Yet, I have been by all means sure that the compiler should not discriminate between explicitly/implicitly defaulted destructors (or constructors as well) when applying these rules. In other words, explicitly defaulted destructor should not be treated as user-defined one (in contrast to an empty user-defined destructor ).
Tested with MSVC 2019 only.
Am I or MSVC right here?
#include <iostream>
class A {
public:
A() = default;
A(const A&) { std::cout << "Auch, they'r making copy of me(?!)" << std::endl; }
A(A&&) { std::cout << "I am moving :)" << std::endl; }
~A() = default;
};
class B : public A {
public:
};
class C : public A {
public:
C() = default;
};
class D : public A {
public:
~D() = default;
};
class E : public A {
public:
E() = default;
E(const E&) = default;
E(E&&) = default;
~E() = default;
};
int main()
{
std::cout << "\n---- A ----\n" << std::endl;
A a;
A a2(std::move(a));
std::cout << "\n---- B ----\n" << std::endl;
B b;
B b2(std::move(b));
std::cout << "\n---- C ----\n" << std::endl;
C c;
C c2(std::move(c));
std::cout << "\n---- D ----\n" << std::endl;
D d;
D d2(std::move(d));
std::cout << "\n---- E ----\n" << std::endl;
E e;
E e2(std::move(e));
}
EXPECTED: Display "I am moving :)" in all cases
ACTUAL : Displays "Auch, they'r making copy of me(?!)" in case D
When you declare a defaulted destructor in D, you disable the compiler-generated move constructor and move assignment operator in D (!), not the base class version. This is why you get the expected output with E, where you override the defaulted operation by the compiler by explicitly = defaulting the special member functions. The compiler-generated move constructor does the right thing for movable base class types, so follow the rule of five and = default the special member functions for D.
Have a look at the table in this answer. It's a very useful reference to be kept under the pillow.

How does the compile choose which constructor to call?

This is my code.
When I delete line 11, the output is
A(0)
B(0)
A(1)
about the last line, "A(1) ", why the second constructor of class A is called?
#include <iostream>
using namespace std;
class A {
public:
A() { cout << "A(0)" << endl; }
A(const A& a) { cout << "A(1)" << endl; }
};
class B {
public:
B() : a() { cout << "B(0)" << endl; }
// B(const B& b) { cout << "B(1)" << endl; }
private:
A a;
};
int main() {
B object1;
B object2 = object1;
return 0;
}
A(0)
B(0)
A(1)
When
B(const B& b) { cout << "B(1)" << endl; }
is commented out/deleted the compiler generates a copy constructor for you. This provided copy constructor will copy all of the members of the class so in this case it will stamp out a copy constructor that looks like
B(const B& copy) : a(copy.a) {}
This is why you see a's copy constructor called.
When you do not comment out/delete
B(const B& b) { cout << "B(1)" << endl; }
You do not copy a because you do not tell it to do so. What the compiler does instead is creates a default initialization for it by transforming the constructor to
B(const B& b) : a() { cout << "B(1)" << endl; }
so the default constructor is called instead of the copy constructor.
The compiler is generating a copy constructor for you, which copies the member a. In order to copy member a, it calls its copy constructor in turn, which prints A(1).
Because object2 is initialized with the implicit copy constructor of B. An implicit copy constructor implicitly copy all the data members of the class, hence the call of the copy constructor of A, which prints "A(1)".
The issue you've run into has to do with thinking commenting out line 11 means you've deleted that constructor.
In C++, there are a couple of constructors that are automatically generated if you ended up using them, even if you didn't declare them yourself. The copy-constuctor, which has the same signature as the commented-out constructor in B, is one of them.
In your case, you end up first calling the default constructor for B, which first constructs it's member A using the default constructor as well. This should give the output you see, where the body of A's copy-constructor is reached before the body of B's because of member initialization ordering.
Then, you make a new object of type B using the assignment operator which implicitly calls the now-generated copy constructor of B. That means A's copy constructor gets called as well, which is a rule in how B's copy-constructor is auto generated. With A's copy-constuctor un-commented, it gets called with the printout.
The compiler for the class B (with the commented copy constructor) defines implicitly the default copy constructor that calls copy constructors for class members.
From the C++ 20 Standard (11.3.4.2 Copy/move constructors)
14 The implicitly-defined copy/move constructor for a non-union class
X performs a memberwise copy/move of its bases and members...
The implicitly defined default copy constructor of the class B looks like
B( const B &b ) : a( b.a )
{
}
Here is a demonstrative program
#include <iostream>
using namespace std;
class A {
public:
A() { cout << "A(0)" << endl; }
A(const A& a) { cout << "A(1)" << endl; }
};
class B {
public:
B() : a() { cout << "B(0)" << endl; }
// An analogy of the implicitly declared copy constructor
B(const B& b) : a( b.a ){}
private:
A a;
};
int main() {
B object1;
B object2 = object1;
return 0;
}
The program output will be the same as if to remove the copy constructor of class B that corresponds to the implicitly generated copy constructor by the compiler.
A(0)
B(0)
A(1)

Surprising behavior in multiple copy constructor inheritance

Starting with C++11, there can be two copy constructors, one taking a parameter of type T&, and one taking a parameter of type const T&.
I have a situation where (seemingly) adding a second copy constructor causes neither one to get called, when the constructors are inherited in a derived class. The copy constructor is overridden by a templatized constructor when both are present.
Here is a MWE:
struct A {
template <typename... Args>
A (Args&&... args)
{ std::cout << "non-default ctor called\n"; }
A (A&) { std::cout << "copy ctor from non-const ref\n"; }
};
struct C :public A { using A::A; };
int main() {
C c1;
C c2(c1);
}
Running this code, we see output
non-default ctor called
copy ctor from non-const ref
which is as expected.
However, adding an additional constructor to struct A as follows:
A (const A&) { }
somehow causes the other copy constructor not to get called, so the output becomes
non-default ctor called
non-default ctor called
In my use case, I want to inherit all the constructors from a base class into a derived class, including the copy constructors and anything else. But it seems that somehow the two copy constructors don't get inherited when they are both present. What is going on here?
From https://en.cppreference.com/w/cpp/language/using_declaration
If one of the inherited constructors of Base happens to have the signature that matches a copy/move constructor of the Derived, it does not prevent implicit generation of Derived copy/move constructor (which then hides the inherited version, similar to using operator=).
So
struct C :public A { using A::A; };
is
struct C :public A
{
using A::A;
C(const C&) = default;
C(C&&) = default;
};
where C(const C&) = default; is similar to
C(const C& c) : A(static_cast<const A&>(c)) {}
So with
struct A {
template <typename... Args>
A (Args&&... args)
{ std::cout << "non-default ctor called\n"; }
A (A&) { std::cout << "copy ctor from non-const ref\n"; }
};
template constructor is chosen, but
struct A {
template <typename... Args>
A (Args&&... args)
{ std::cout << "non-default ctor called\n"; }
A (const A&) { std::cout << "copy ctor from const ref\n"; }
A (A&) { std::cout << "copy ctor from non-const ref\n"; }
};
A (const A&) is chosen.
As you can notice, there is also a defect:
The semantics of inheriting constructors were retroactively changed by a defect report against C++11. Previously, an inheriting constructor declaration caused a set of synthesized constructor declarations to be injected into the derived class, which caused redundant argument copies/moves, had problematic interactions with some forms of SFINAE, and in some cases can be unimplementable on major ABIs. Older compilers may still implement the previous semantics.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0136r1.html
With that defect, your class C would be
struct C :public A
{
using A::A;
template <typename ...Ts>
C(Ts&&... ts) : A(std::forward<Ts>(ts)...) {} // Inherited.
C(const C&) = default;
C(C&&) = default;
};
So you call C(C& c) : A(c) {} (after template substitution).

I'm receiving error when calling base copy constructor

Im receiving error C2082: redefinition of formal parameter 'rval' in this code while trying to call base copy ctor explicitly:
#include <iostream>
using namespace std;
class Base
{
public:
Base(const Base& rhs){ cout << "base copy ctor" << endl; }
};
class Derived : public Base
{
public:
Derived(const Derived& rval) { Base(rval) ; cout << "derived copy ctor" << endl; }
// error C2082: redefinition of formal parameter 'rval'
};
int main()
{
Derived a;
Derived y = a; // invoke copy ctor
cin.ignore();
return 0;
}
However if do it like this:
Derived(const Derived& rval) { Base::Base(rval) ; cout << "derived copy ctor" << endl; }
then is OK.
Why am I asking this?
according to the answers on StackOwerflow
I do not have to use operator :: to access base copy ctor,
so why do I receive this error?
btw: I'm using visual studio 2010.
I'm having one more question:
Do I have to call base's move constructor in user defined move constructor of derived class?
To call the base constructor you need to put the call in the member initalization list
class Derived : public Base
{
public:
Derived(const Derived& rval) : Base(rval)
{
cout << "derived copy ctor" << endl;
}
};
Assuming that you mean 'move' constructor is the copy constructor - Yes. You will have to call the Base's constructor. Otherwise the definition if the base object within the derived object will not be complete. You can either call a copy constructor or a normal constructor of the base class.