Constructor inheritance behaving weirdly - c++

In the following situation:
class A
{
protected:
int m_int;
A() : m_int{-2} {};
public:
A(const A& a) { m_int = a.get(); }
A& operator=(const A& a) { m_int = a.get(); return *this; }
int get() const { return m_int; }
};
class B : public A
{
protected:
using A::m_int;
public:
// same constructors as parent, but public
using A::A;
};
class C : public B
{
public:
// same constructors as parent
using B::B;
C(int x) { m_int = x; }
C(const B& b) { C(b.get()); }
};
class D : public C
{
public:
// same constructors as parent
using C::C;
D(const B& b) { D(b.get()); }
};
int main()
{
C c(5);
D d(c);
return d.get();
}
I would expect for main() to return 5. Instead, it returns -2. It seems it's calling the default constructor of A, whereas I would have expected for it to call the D(const B& b) constructor. What's going on?
Why do I need to provide an explicit constructor for C(const B& b)? I would have guessed for a default behavior to be deduced, for inheritance reasons. What am I missing?

The problem is the constructor in D:
D(const B& b) { D(b.get()); }
This will be the constructor that is called. And it doesn't copy the value from b, instead it creates a new and temporary D object which is promptly destructed as the constructor function exit.
You have the same problem in the corresponding C constructor:
C(const B& b) { C(b.get()); }
You should use a constructor initializer list to delegate to the correct constructor:
D(const B& b) : C(b.get()) {}

Related

Delegating to a default C++ constructor

Assume a C++ class A with members that can all be copied by the their respective copy constructors. We can rely on the default copy constructor for A to copy its members:
class A {
private:
A(const A&) = default; // We don't really need this line
int a;
B b;
double c;
}
But now let's assume that I want to "extend" (or annotate) the default constructor of A so that in addition to copying its members, it does something else, e.g. writes a line to a log file.
I.e. I'd like to write something like:
class A {
public:
A(const A& a) : A::default(A) {
print("Constructing A\n");
}
private:
// like before
}
Unfortunately that is not correct C++ syntax.
So is there a syntax which allows delegating to a default C++ constructor while explicitly defining the same constructor?
The simplest is to move all members into a base class and write the message in a derived class:
class A_helper {
private:
int a;
B b;
double c;
};
class A : public A_helper {
public:
A() = default;
A(const A& a) : A_helper(a) {
print("Constructing A\n");
}
A(A&& a) : A_helper(std::move(a)) {
print("Constructing A\n");
}
};
You can delegate to the default constructor like you would default-initialize a member variable:
A(const A&) : A()
{
...
}
As often, "We can solve any problem by introducing an extra level of indirection." ( "…except for the problem of too many levels of indirection,"):
struct VerboseA
{
VerboseA() = default;
VerboseA(const VerboseA&) { print("Constructing A\n"); }
VerboseA(VerboseA&&) { print("Constructing A\n"); }
};
And then
class A : VerboseA // EBO (Empty Base Optimisation)
{
private:
A(const A&) = default; // We don't really need this line
int a;
B b;
double c;
};
or in C++20
class A {
private:
A(const A&) = default; // We don't really need this line
[[no_unique_address]]VerboseA verbose; // "EBO" equivalent with attribute
int a;
B b;
double c;
};

How to copy a class in C++?

Suppose I have class A
class A{
public:
A(const A&) {};
A() {};
~A() {};
bool bln;
B b;
}
If I write
A *a2;
a2 = new A(*a);
where a is an A object, then a2->b seems different to a->b.
How can I exactly copy a to a2?
bln is not being initialized in any of A's constructors. b is only being default constructed in both constructors, and is not being copied at all in the copy constructor.
Try this:
class A
{
public:
bool bln;
B b;
A() : bln(false) {}
A(const A &src) : bln(src.bln), b(src.b) {}
};
A better option is to simply let the compiler generate a default copy constructor for you, which will effectively be the same as above:
class A
{
public:
bool bln;
B b;
A() : bln(false) {}
};
Or, in C++11 and later, you can do use:
class A
{
public:
bool bln = false;
B b;
// these are optional in this case and can be omitted
A() = default;
A(const A&) = default;
};

How does the abstract base class avoid the partial assignment?

As is talked about in Item 33 in "More Effective C++", the assignment problem is
//Animal is a concrete class
Lizard:public Animal{};
Chicken:public Animal{};
Animal* pa=new Lizard('a');
Animal* pb=new Lizard('b');
*pa=*pb;//partial assignment
However, if I define Animal as an abstract base class, we can also compile and run the sentence:*pa=*pb. Partial assignment problem is still there.
See my example:
#include <iostream>
class Ab{ private: int a;
double b;
public:
virtual ~Ab()=0;
};
Ab::~Ab(){}
class C:public Ab{
private:
int a;
double b;
};
class D:public Ab{
private:
int a;
double b;
};
int main()
{
Ab *pc=new C();
Ab *pd=new D();
*pc=*pd;
return 0;
}
Do I miss something? Then what's the real meaning of the abstract base class?
I got the answer by myself. I missed a code snippet in the book.
Use protected operator= in the base class to avoid *pa=*pb. Use abstract base class to avoid animal1=animal2.Then the only allowed expressions are lizard1=lizard2;chicken1=chicken2;
See the code below:
#include <iostream>
class Ab{
private:
int a;
double b;
public:
virtual ~Ab()=0;
protected: //!!!!This is the point
Ab& operator=(const Ab&){...}
};
Ab::~Ab(){}
class C:public Ab{
public:
C& operator=(const C&){...}
private:
int a;
double b;
};
class D:public Ab{
public:
D& operator=(const D&){...}
private:
int a;
double b;
};
int main()
{
Ab *pc=new C();
Ab *pd=new D();
*pc=*pd;
return 0;
}
The abstract base class cannot help in case of assignment because the base sub-object is not instantiated (what an abstract class would block) but is sliced off the derived object (i.e. the assignment is done between already existing base sub-objects).
To avoid the problem the only solution I can think to is
make the assignment virtual
check in the assignment that the source instance is of the correct type
In code
#include <iostream>
struct Base {
int bx;
Base(int bx) : bx(bx) {}
virtual Base& operator=(const Base& other) {
bx = other.bx;
return *this;
}
};
struct A : Base {
int x;
A(int bx, int x) : Base(bx), x(x) {}
A& operator=(const Base& other) {
const A& other_a = dynamic_cast<const A&>(other);
Base::operator=(other);
x = other_a.x;
return *this;
}
};
struct B : Base {
int x;
B(int bx, int x) : Base(bx), x(x) {}
B& operator=(const Base& other) {
const B& other_b = dynamic_cast<const B&>(other);
Base::operator=(other);
x = other_b.x;
return *this;
}
};
The dynamic_cast<const A&>(other) is the operation that will fail if the object passed to the assignment operator is not of the correct derived type (it can be a sub-derived object, but this should be logically ok for an assignment source).
As an example:
int main(int argc, const char *argv[]) {
Base *pa1 = new A(1, 2);
Base *pa2 = new A(3, 4);
Base *pb1 = new B(5, 6);
Base *pb2 = new B(7, 8);
*pa1 = *pa2; std::cout << pa1->bx << "/" << dynamic_cast<A*>(pa1)->x << "\n";
*pb1 = *pb2; std::cout << pb1->bx << "/" << dynamic_cast<B*>(pb1)->x << "\n";
std::cout << "Ok so far\n";
*pa1 = *pb1; // Runtime error here (bad cast)
return 0;
}
It doesn't matter that your base class has pure virtual functions because you haven't defined the operator= for any of the classes. So when the compiler sees this statement:
*pc=*pd;
where pc and pd are both of type Ab, it will call the default assignment operator for Ab, which will result in partial assignment. As in the following example, I get the output as "Abstract Base" which is from abstract base class:
class A {
public:
virtual void foo() =0;
virtual A& operator=(const A& rhs) {
std::cout << "Abstract Base";
return *this;
}
};
class B : public A {
public:
virtual void foo() {
std::cout << "b:foo";
}
};
class C : public A {
public:
virtual void foo() {
std::cout << "c:foo";
}
};
int main()
{
A* b = new B();
A* c = new C();
*b = *c;
return 0;
}
Since you have not handled the assignment operators in your classes, you land up in situation partial assignment as Scot clearly describes in his article.
You need to handle assignments in your classes. In current design default implicit assignment operator of Ab is called and thus all the properties of children class are lost.
To avoid this you should have an implementation like this:
class Ab{ private: int a;
double b;
public:
virtual ~Ab()=0;
virtual Ab& operator=(const Ab& rhs){cout<<"in Ab="<<endl;}
};
Ab::~Ab(){}
class C:public Ab{
C& operator=(const Ab& rhs){cout<<"in C="<<endl;
return operator=(dynamic_cast<const C&>(rhs)); }
C& operator=(const C& rhs){
cout<<"do somethin in C="<<endl;
}
private:
int a;
double b;
};
class D:public Ab{
D& operator=(const Ab& rhs){cout<<"in D="<<endl;
return operator=(dynamic_cast<const D&>(rhs)); }
D& operator=(const D& rhs){
cout<<"do somethin in D="<<endl;
}
private:
int a;
double b;
};

Getting the move constructor/assignment operator to be used

Consider the following code:
#include <iostream>
#define P_(x) std::cout << x << std::endl
class B {
public:
B() { P_("B::B()"); }
B(const B&) { P_("B::B(const B&)"); }
B(B&&) { P_("B::B(B&&)"); }
~B() { P_("B::~B()"); }
B& operator=(const B&) { P_("B::op=(const B&)"); return *this; }
B& operator=(B&& b) { P_("B::op=(B&&)"); return *this; }
};
class Foo {
public:
void setB(const B& b) { mB = b; }
private:
B mB;
};
B genB() {
return B();
}
int main() {
Foo f;
f.setB(genB());
}
Suppose B is a type that is difficult to copy-construct. I'd like to generate some B (with the function genB) and store it in a Foo. Since genB returns a temporary result, I'd expect that a move constructor would be used.
However, when I run the code, I get this output:
B::B()
B::B()
B::op=(const B&)
B::~B()
B::~B()
This clearly shows that two B's get created and destroyed, but that the second is a copy, and not a move of the first.
What is the best way to get move constructors used whenever possible?
Do I need to call std::move() somewhere?
Do I need a separate overload for a B& and a B&&?
Is there something else entirely that I'm missing?
You could overload that setB function:
class Foo {
public:
void setB(const B& b) { mB = b; }
void setB(B&& b) { mB = std::move(b); }
private:
B mB;
};
Or, you can use the "pass by value" way:
class Foo {
public:
void setB(B b) { mB = std::move(b); }
private:
B mB;
};
Here, the parameter b will be move constructed when possible or copy constructed otherwise.
The first B instance is the one created when creating your Foo instance :
Foo f;
This is because your Foo class has a B member called mB.
The second B instance is the one created by the genB() call.
The assignment operator is called because of the assignment you perform in the Foo::setB function :
mB = b;
Nowhere is there a chance to use a copy or move constructor.

Calling another constructor when constructing an object with const members

I have a class with const members, and one constructor which calls another constructor with extra values filled in. Normally I could use a colon initializer for this, but the function is complex (printf/sprintf-like) and requires me to use a variable on the stack, so I have to do this in the body of the constructor and use assign *this to the new object. But of course this is invalid, because my member variables are const.
class A
{
public:
A(int b) : b(b), c(0), d(0) // required because const
{
int newC = 0;
int newD = 0;
myfunc(b, &newC, &newD);
*this = A(b, newC, newD); // invalid because members are const
// "cannot define the implicit default assignment operator for 'A', because non-static const member 'b' can't use default assignment operator"
// or, sometimes,
// "error: overload resolution selected implicitly-deleted copy assignment operator"
};
A(int b, int c, int d) : b(b), c(c), d(d) { };
const int b;
const int c;
const int d;
};
A a(0);
(I haven't explicitly deleted the assignment operator.) I declared the members const because I would like them to be public, but not mutable.
Is there some canonical way of solving this problem without using scary casts and force-overriding the members' constness? What's the best solution here?
You can add a parameters class and use either C++11 constructor delegation or a base class:
struct parameters {
int b; int c; int d;
parameters(int b): b(b), c(), d() {
myfunc(b, &c, &d);
}
};
// constructor delegation
class A {
public:
A(int b): A(parameters(b)) { }
A(parameters p): b(p.b), c(p.c), d(p.d) { }
};
// base/wrapper
class ABase {
ABase(parameters p): b(p.b), c(p.c), d(p.d) { }
};
class A: public ABase {
public:
A(int b): ABase(parameters(b)) { }
};
How about making a helper function:
class A
{
static int initializor(int b) { int n; myfunc(b, &n); return n; }
public:
explicit A(int b_) : b(b_), c(initializor(b_)) { }
A(int b_, int c_) : b(b_), c(c_) { }
// ... as before ...
};
I prefer Kerrek SB's answer, but in your case there is the complication that you can't easily make separate initialisation functions for each member.
In that case, another solution is to move the members to a base class and initialize that base class with a helper class with non-const members. Your initialization code is moved to the helper class' constructors, and can assign without problems.
class A_init
{
public:
A_init(int b)
{
// do whatever you like with c and d:
c = ...;
d = ...;
}
int c; // Note: non-const
int d; // Note: non-const
};
class A_base
{
public:
A_base(int b, A_init init) : b(b), c(init.c), d(init.d) {}
A_base(int b, int c, int d) : b(b), c(c), d(d) {}
const int b;
const int c;
const int d;
};
class A : public A_base
{
public:
A(int b) : A_base(b, A_init(b)) {}
A(int b, int c, int d) : A_base(b, c, d) {}
};
If one wants restrict access to A_init, one can switch to private and declare A a friend.
Where to put the results of myfunc so it can be set and used from different mem-initializers? How about in a default argument?
class A
{
private:
struct InitData;
public:
A(int b, InitData data=InitData());
A(int b, int c, int d) : b(b), c(c), d(d) { };
const int b;
const int c;
const int d;
};
struct A::InitData
{
int setup(int b);
int c;
int d;
};
inline int A::InitData::setup(int b)
{
myfunc(b, &c, &d);
return b;
}
inline A::A(int b_, InitData data)
: b(data.setup(b_)),
c(data.c),
d(data.d) {}
A a(0);
Since the made up type is private and has no conversions, there's little risk of accidentally using it or abusing it.