a concerned citizen
a concerned citizen

Reputation: 849

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?

Upvotes: 0

Views: 72

Answers (1)

grek40
grek40

Reputation: 13458

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.

Upvotes: 4

Related Questions