Reputation: 403
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
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
Reputation: 206577
The solution to your problem may lie in changing your thought on the responsibilities of various classes.
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) { ... }
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
Reputation: 15446
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