0

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.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
a concerned citizen
  • 787
  • 2
  • 9
  • 25
  • 3
    I don't really understand how `Last` *is-a* A and *is-a* B but then *has-a* either A or B... doesn't make sense to me. – nvoigt Nov 05 '16 at 16:09
  • 2
    `m_p` is not your base object. `this` is. – Captain Giraffe Nov 05 '16 at 16:09
  • @CaptainGiraffe You mean, instead of using `m_p->m_x`, I should use `this->m_x`? I just replaced `m_p` with `this` and it compiles, but it doesn't seem to work. Variables are 0, all. Could you please elaborate? – a concerned citizen Nov 05 '16 at 16:21
  • 2
    @aconcernedcitizen why wouldn't they be zero? Can you expand on this example by showing what is in `Base`, `A`, and `B`? – Ryan Haining Nov 05 '16 at 16:22
  • 1
    Could you post complete code and explain your design in detail? – curiousguy Nov 05 '16 at 16:24
  • @nvoigt The diagram here http://www.learncpp.com/cpp-tutorial/117-multiple-inheritance/ (a bit lower on the page) could serve better than my words. Did I not implement it as it should have been? – a concerned citizen Nov 05 '16 at 16:24
  • 2
    You implemented multiple inheritance and the dreaded diamond... and then put something on top that obviously is a unique pointer to *another* instance of either A or B... we don't understand your problem, most likely because we don't know what you want to do with this. – nvoigt Nov 05 '16 at 16:27
  • Can you provide some real code that gives a reproducible error message? – Galik Nov 05 '16 at 16:49

2 Answers2

4

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.

Community
  • 1
  • 1
Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
  • Then, if they are "red herrings", can they be avoided? I edited the answer, I hope it's clearer now. – a concerned citizen Nov 05 '16 at 17:14
  • 1
    @aconcernedcitizen: They can be avoided by presenting a [MCVE] in the question. – Lightness Races in Orbit Nov 05 '16 at 17:20
  • @LightnessRacesinOrbit I did edit the answer with the 2nd example. At large, it's basically what I'd like to do. Is it not good enough? – a concerned citizen Nov 05 '16 at 17:24
  • 1
    @aconcernedcitizen: You asked how the red herring can be avoided. I'm just pointing out that the responsibility to do that is _yours_, by producing a _minimal_ working example that does not contain any extraneous and distracting features. – Lightness Races in Orbit Nov 05 '16 at 17:34
  • @aconcernedcitizen: Your question still makes it sound as if you suspect virtual inheritance and `std::unique_ptr` to cause your problem. But they don't, they don't have anything to do with the problem at all. Once you solve the basic, underlying problem, you can easily adapt your solution to one involving all those extra features. – Christian Hackl Nov 05 '16 at 17:47
  • @ChristianHackl You're right, but I am not rigid, if I can change it to get what I need, I'll change it. Maybe I'll try composition, instead, I am digging through every answer I can find (that didn't come up before or while posting) and this: https://stackoverflow.com/questions/12500230/diamond-inheritance-lowest-base-class-constructor suggests composition. The ugliest part is that I cannot give any real code, only approximations and they are always prone to confusion. I'm really sorry that I can't. At any rate, the comments and your answers did reveal things unknown to me, thank you. – a concerned citizen Nov 05 '16 at 18:08
  • @aconcernedcitizen: Favouring composition over inheritance is certainly a good principle in any kind of object-oriented programming. This is acknowledged in all the classical literature on the subject (e.g. the seminal Design Patterns book). – Christian Hackl Nov 05 '16 at 18:11
2

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.

Community
  • 1
  • 1
  • I'm afraid I can't really post the code, but I hope the example from the newly edited question makes more sense. – a concerned citizen Nov 05 '16 at 17:15
  • No, I'm afraid it doesn't. You've just replaced the names, but I still don't see what you are trying to do. What does the base class "Power" represent? – Martin Bonner supports Monica Nov 05 '16 at 17:23
  • I gave the example after the link in my question, I suppose you could think of it as the power supply and maybe the controlling unit. So you could also say that `m_x` is the storage (RAM, USB, harddisk, etc) where the documents get stored after scanning, and then used for editing/printing/etc. It's an abstract example. Or you could think of industrial machine factory, making tractors, bulldozers, etc, which all start working, one at a time, as called, in a working site. Anything along these lines. – a concerned citizen Nov 05 '16 at 17:37
  • In that case, don't make the m_x a base of Printer/Scanner - make it a separate object which you pass to the constructor of Printer/Scanner. In general, prefer containment to inheritance. – Martin Bonner supports Monica Nov 05 '16 at 20:58
  • I intend to switch to composition, instead, and change everything, even if it turns out to be wrong, too, but I think it has greater chances of succeeding. Maybe then `protected` will turn out easier to tame, the way I expected it to? – a concerned citizen Nov 06 '16 at 06:43
  • @aconcernedcitizen: With composition, there is no such thing as `protected`. The inheritance which is left in a good object-oriented design will usually just be `public` (for non-virtual functions other than the destructor) and `private` (for virtual functions other than the destructor). You will use `protected` very rarely. But mind that all of these are guidelines and not strict rules. Good software design takes years of experience and a lot of compromises. – Christian Hackl Nov 06 '16 at 09:30