Search code examples
c++unique-ptrdiamond-problem

Can't access protected member variables of the most base class through std::unique_ptr in diamond


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.


Solution

  • 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:

    1. The Last object which is being created by the constructor, i.e. the one pointed to by this. It's also a Base object because of the is-a inheritance relationship.
    2. 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.