I am not an advanced programmer. Suppose there is a classic diamond inheritance:
class Base
class A: virtual public Base
class B: virtual public Base
class Last: public A, public B
Suppose Base
has a variable, m_x
, that is common to both A
and B
, such that only one of A
, or B
, can be called at a time, not both (which is what is needed). To get around this, this is used:
class Last: public A, public B
{
private:
std::unique_ptr<Base> m_p;
public:
Last(int i)
{
if (i)
m_p = std::unique_ptr<Base>(new A());
else
m_p = std::unique_ptr<Base>(new B());
}
};
This is fine, but now m_p->m_x
cannot be accessed anymore because it says it's protected, but both A
and B
call m_x
in their constructors directly, with no problems.
Is this a known limitation or is this the wrong way to do it? If it's wrong, what solutions are there?
Here is some code based on the diagram found here (a bit lower on the page):
#include <iostream>
#include <memory>
class Power
{
protected:
double m_x;
public:
Power() {}
Power(double x): m_x {x} {}
virtual ~Power() = default;
};
class Scanner: virtual public Power
{
public:
Scanner() {}
Scanner(double x): Power(x) {} // scan document
};
class Printer: virtual public Power
{
public:
Printer() {}
Printer(double x): Power(x) {} // print document
};
class Copier: public Scanner, public Printer
{
private:
std::unique_ptr<Power> m_p;
public:
Copier() {}
Copier(double x, int i)
{
if (i)
m_p = std::unique_ptr<Power>(new Scanner(x));
else
m_p = std::unique_ptr<Power>(new Printer(x));
}
void print() { std::cout << this->Power::m_x << '\n'; }
};
int main(int argc, char *argv[])
{
Copier *copier {new Copier(1.618, 0)};
copier->print();
copier = new Copier(3.14, 1);
copier->print();
return 0;
}
Using both this->m_p
and this->Power::m_x
(according to answers and comments) compiles, but the output is 0
.
To be sure I spell it out all: not only I am quite a beginner, but, given the example above, it oesn't really have to stay that way if there is another alternative to call Scanner
or Printer
only one at a time from inside Copier
. I am not asking for opinions, I understand it's forbidden, but I won't reject them coming from more experienced users. After all, I am learning.
Both virtual inheritance and std::unique_ptr
are red herrings. The problem comes down to this:
class Base
{
protected:
int m_x;
};
class Last : public Base
{
public:
Last()
{
Base base;
base.m_x = 0; // error
m_x = 1; // no error
}
};
The error is something like error C2248: 'Base::m_x': cannot access protected member declared in class 'Base'
or error: 'int Base::m_x' is protected within this context
.
The explanation is that protected
is a somewhat special case. It does not only work on class level but also on the object level. And you have two relevant objects here:
- The
Last
object which is being created by the constructor, i.e. the one pointed to bythis
. It's also aBase
object because of the is-a inheritance relationship. - The local object named
base
within the constructor.
Now, the problem is that in the line base.m_x = 0;
, you are in the context of the first object and not the second one. In other words, you are trying to access the m_x
of base
from outside base
. C++ simply does not allow this.
A very technical explanation can be found in the C++ standard at §11.4 [class.protected]
, a more easily understandable one in an excellent answer here on Stack Overflow.
protected
doesn't mean quite what you think it does.
Although Last
is derived from Base
, member functions of Last
don't have access to the protected members of any Base
object - just those Base
objects that are sub-objects of some Last
object.
So you can write: this->Base::x
because *this
is a Last
object, but not m_p->x
, because *m_p
is of static type Base
.
As others have noted, I think this is actually an XY problem. Having an object which derives from two classes, and then also has a pointer to another object of one of those classes is very strange indeed. I think you need to clarify what you are trying to do.
来源:https://stackoverflow.com/questions/40440337/cant-access-protected-member-variables-of-the-most-base-class-through-stduniq