a concerned citizen a concerned citizen - 3 months ago 7
C++ Question

C++ Derived classes won't work without default parameters, even then wrong

Suppose I have a base class

A
, two derived classes
B
and
C
which are
virtual public A
, and a final class
D
, which is
public B, public C
. I tried to make a working code for this:

#include <iostream>

class A
{
protected:
int m_x;
public:
A(int a): m_x {2 * a} { std::cout << "A()" << '\n'; }
};

class B : virtual public A
{
private:
int m_y, m_z;
public:
B(int a): m_y {a * a}
{
m_z = 2 * m_y + 1;
A(m_z);
std::cout << "B()" << '\n';
}
int getVal() { return m_y; }
};

class C : virtual public A
{
private:
int m_p, m_q;
public:
C(int a): m_p {1 - a}
{
m_q = m_p - 3 * a;
A(m_q);
std::cout << "C()" << '\n';
}
int getVal() { return m_p; }
};

class D : public B, public C
{
private:
int m_w;
public:
D(int a, int b)
{
switch (b)
{
case 1: { B(a); break; }
case 2: { C(a); break; }
}
m_w = m_x * a;
std::cout << "D()" << '\n';
}

int getVal() { return m_z; }
};

int main(int argc, char *argv[])
{
D d {5, 1};
std::cout << d.getVal() << '\n';

return 0;
}


All classes normally have their own
.h
and
.cpp
file, I compacted it a bit and the formulas are pure fiction. The short story is that
A
acts as an interface class who has
m_x
as a common variable for both
B
and
C
, to be used and modified as needed, while
D
uses
A
's variable to further calculate the desired
m_w
. The two virtual classes are called on a need basis, from
D
.

My problem: I can't compile the above if no default parameters are given, but if they are, the results are wrong.

a) As it is (no default parameters), first error line:

16:29 no matching function for call to A::A()


b) No default parameters, change line 19 to
A::A(m_z)
:

cannot call constructor ‘B::A’ directly [-fpermissive]


(I think this makes sense, no such thing as
B::A
)

c) With default parameters: A=2, B=3, C=4, outputs:

A()
A()
B()
A()
C()
A()
A()
B()
D()
10


which looks awful enough (I don't know why so many calls), but it should also be:
B(5)
=>

m_y = 5 * 5 = 25


m_z = 2 * 25 + 1 = 51


A(51)
=>

m_x = 2 * 51 = 102


finally:

m_w = 102 * 5 = 510


...shouldn't it? Can someone please tell me what I am doing wrong?

Answer

You need to call base class constructors in the initializer list, not in the constructor body.

Replace

B(int a): m_y {a * a}
{
    m_z = 2 * m_y + 1;
    A(m_z);
    std::cout << "B()" << '\n';
}

By

B(int a): A(2 * a * a + 1), m_y {a * a}
{
    m_z = 2 * m_y + 1;
    std::cout << "B()" << '\n';
}

And so on.

In class D you need to call constructors for A, B and C, since C++ doesn't allow second guessing, whether the A constructor would be called from B or C when D is created.


Take 2

Since you actually want D to conditionally construct an instance of B or C, you need to reorganize your design.

Suppose I have temperature readings (A) from different countries (B, C, etc), and I have to filter/present/display them on screen/graph/somehow (D) (just an example).

So you have different measurement units of temperatures and you want to display them together in the same measurement unit. For my personal convenience, I say that the target unit is °C.

Lets design a temperature class and some helpers and factory methods

int CelsiusToFahrenheit(int c)
{ return /* somehow calculate F from c */; }

int FahrenheitToCelsius(int f)
{ return /* somehow calculate °C from f */; }


class Temperature
{
protected:
    int m_temperatureCelsius;
public:
    Temperature(int temperatureCelsius): m_temperatureCelsius { temperatureCelsius }
    { std::cout << "A()" << '\n'; }

    int GetCelsius()
    { return m_temperatureCelsius; }

    int GetFahrenheit()
    { return CelsiusToFahrenheit(m_temperatureCelsius); }
};

Temperature* CreateTemperatureFromCelsius(int c)
{ return new Temperature(c); }

Temperature* CreateTemperatureFromFahrenheit(int f)
{ return new Temperature(FahrenheitToCelsius(f)); }

Now if you have different countries and want to register a temperature per country item, you could define countries as follows (note: there are MANY other ways)

class BaseCountry
{
    protected:
        Temperature* m_temperature;
    public:
        BaseCountry()
        { }

        virtual ~BaseCountry() = 0; // abstract class

        int GetCountryTemperatureCelsius()
        { return m_temperature ? m_temperature->GetCelsius() : 0; }
}

class CountryA : public BaseCountry
{
    public:
        CountryA(int temperatureC)
        { m_temperature = CreateTemperatureFromCelsius(temperatureC); }

        ~CountryA()
        { delete m_temperature; }
}


class CountryB : public BaseCountry
{
    public:
        CountryB(int temperatureF)
        { m_temperature = CreateTemperatureFromFahrenheit(temperatureF); }

        ~CountryB()
        { delete m_temperature; }
}


int main(int argc, char *argv[])
{
    BaseCountry& c1 = CountryA(10);
    BaseCountry& c2 = CountryB(10);

    // display 10
    std::cout << c1.GetCountryTemperatureCelsius() << '\n';
    // display the number in °C for 10 F
    std::cout << c2.GetCountryTemperatureCelsius() << '\n';

    return 0;
}

In a display/screen/diagram class, you can hold an array of CountryBase references and access their temperature normalized to °C, no matter how they where constructed.

Comments