mib0163
mib0163

Reputation: 403

Accessing a parent member in class constructor's initialization list

Suppose the following example, where SecondClass inherits from FirstClass and its constructor is supposed to do the same stuff as FirstClass's constructor. I do not want to repeat it, so I do:

SecondClass(int x)  : FirstClass(x) {...}

At the same time, I want to populate name member and each class should have a different value. In FirstClass, I do this with

FirstClass(int x) : BaseClass("first"), x(x) {...}

In SecondClass, I have tried

SecondClass(int x) : FirstClass(x), name("second")

which fails (obviously) to compile, because "class 'SecondClass' does not have any field named 'name'".

So, how can I achieve what I want to have? I do not want to pass name in each class constructor as a parameter (which seems to be a standard approach) as it is supposed to be fixed and not be set by a caller.

Full example code:

#include <iostream>
#include <string>
using namespace std;

class BaseClass {
protected:
    string name;
public:
    BaseClass(const string name) : name(name) {};

    virtual void doSomething() {};
};

class FirstClass : public BaseClass {
protected:
    int x;
public:
    FirstClass(int x) : BaseClass("first"), x(x) {
        cout << "FirstClass constructor, name=" << this->name << endl;
        // do something more with `x`
    };

    virtual void doSomething() {
        // do something with `x`
    }
};

class SecondClass : public FirstClass {
public:
    // SecondClass's constructor should do the same as FirstClass,
    // but I want this->name to contain "second"
    SecondClass(int x)  : FirstClass(x), name("second")
    {
        cout << "SecondClass constructor, name=" << this->name << endl;
    };

    virtual void doSomething() {
        // do something else with `x`
    };
};

int main()
{
    BaseClass *c1, *c2;

    c1 = new FirstClass(1);
    c2 = new SecondClass(1);

    c1->doSomething();
    c2->doSomething();
}

Upvotes: 3

Views: 119

Answers (3)

Ted Lyngmo
Ted Lyngmo

Reputation: 117298

Virtual inheritance would "solve" the problem if you inherit both BaseClass and FirstClass in SecondClass. I don't recommend this, but by doing so, when instantiating SecondClass, FirstClass will not re-initialize BaseClass:

#include <iostream>
#include <memory>
#include <string>

using std::cout, std::string;

class BaseClass {
protected:
    string name;

public:
    BaseClass(const string Name) : name(Name) {
        cout << "BaseClass, name=" << this->name << "\n";
    };
    virtual ~BaseClass() {}                               // <- you really DO need this
};

class FirstClass : public virtual BaseClass {
protected:
    int x;

public:
    FirstClass(int X) : BaseClass("first"), x(X) {
        cout << "FirstClass, name=" << this->name << "\n";
    };
};

class SecondClass : public virtual BaseClass, FirstClass {
public:
    SecondClass(int X) : BaseClass("second"), FirstClass(X) {
        cout << "SecondClass, name=" << this->name << "\n";
    };
};

int main() {
    // this is a better alternative than raw base class pointers:
    std::unique_ptr<BaseClass> c1, c2;

    cout << "--- FirstClass ---\n";
    c1 = std::make_unique<FirstClass>(1);
    cout << "--- SecondClass ---\n";
    c2 = std::make_unique<SecondClass>(2);
    cout << "---\n";
}

Output:

--- FirstClass ---
BaseClass, name=first
FirstClass, name=first
--- SecondClass ---
BaseClass, name=second
FirstClass, name=second
SecondClass, name=second
---

Upvotes: 4

R Sahu
R Sahu

Reputation: 206577

The solution to your problem may lie in changing your thought on the responsibilities of various classes.

  1. It is better to leave non-leaf classes in a non-instantiable state. If you follow this guideline, FirstClass will not be instantiable and, hence, the constructor

    FirstClass(int x) : BaseClass("first"), x(x) {...}
    

    does not make sense. Only

    FirstClass(std::string name, int x) : BaseClass(name), x(x) {...}
    

    makes sense.Then, SecondClass fits right it.

    SecondClass(int x)  : FirstClass("second", x) { ... }
    
  2. If "name" is supposed to represent the most derived object type, it is better to use a virtual member function to return that instead of a member variable in the base class.

    class BaseClass 
    {
       ...
       virtual std::string getName() const = 0;
       ...
    };
    
    class FirstClass : public BaseClass 
    {
       ...
       virtual std::string getName() const { return "first"; }
       ...
    };
    
    class SecondClass : public FirstClass 
    {
       ...
       virtual std::string getName() const { return "second"; }
       ...
    };
    

Upvotes: 3

scohe001
scohe001

Reputation: 15446

Create another constructor

You can overload constructors just like any old function, so overload FirstClass's constructor to take a string name:

FirstClass(int x, string name) : BaseClass(name), x(x) {

But then anybody can make FirstClass's with a name that's not "first"! To fix that, you can make this constructor protected, so that only children can call it:

protected:
    FirstClass(int x, string name) : BaseClass(name), x(x) {

And then you can call it from your SecondClass constructor:

SecondClass(int x) : FirstClass(x, "second") {

Upvotes: 4

Related Questions