Unexpected reference behaviour

Viewed 94

I was studing the Composit Pattern and experienced an unexpected behaviour of the following code:

namespace Composit
{

    class Component
    {
    public:
        virtual ~Component() { }
        virtual void operation() = 0;
    protected:
    private:
    };

    class Leaf : public Component
    {
    public:
        Leaf() { }
        virtual ~Leaf() { }
        void operation() override
        {
            // Implementation of operation for a leaf
            std::cout << this << ": I am a leaf" << std::endl;
        }

    protected:

    private:
    };

    class Composit : public Component
    {
    public:

        Composit(Component& x) :
            child_(x)
        { }

        virtual ~Composit() { }

        void operation() override
        {
            // Implementation of operation for a composit
            std::cout << this << ": I am a composit! " << std::endl;
            child_.operation();
        }

    protected:
        Component& child_;

    private:
    };
}

int main()
{
    Composit::Leaf a;
    Composit::Composit b(a);    // b->a
    Composit::Composit c(b);    // c->b->a
    Composit::Composit d(c);    // d->c->b->a
}

I expected, as shown in the comments, to have some form of a recursive link. Instead I discovered that "child_" for "b", "c" and "d" reference "a". I can get the expected behaviour when the parameter to the Composit constructor is a pointer to a Component.

Composit(Component* x) :
                child_(*x)
            { }

and the definitions of the variables b, c, d obviously use the addresses of a, b, c

Composit::Leaf a;
Composit::Composit b(&a);    // b->a
Composit::Composit c(&b);    // c->b->a
Composit::Composit d(&c);    // d->c->b->a

Is there a reason for the version using the reference as a parameter not to work as I expected?

Thanks.

Edit: I tried this code

void f(Composit::Component& x)
{
    x.operation();
}



int main()
{
    Composit::Leaf a;
    Composit::Composit b(a);    // b->a
    Composit::Composit c(b);    // c->b->a
    Composit::Composit d(c);    // d->c->b->a

    f(d);
    f(c);
    f(b);
    f(a);

}

and got this output

00D7F858: I am a composit!
00D7F888: I am a leaf
00D7F868: I am a composit!
00D7F888: I am a leaf
00D7F878: I am a composit!
00D7F888: I am a leaf
00D7F888: I am a leaf

This is instead what I get when I pass the pointers

int main()
{
    Composit::Leaf a;
    Composit::Composit b(&a);    // b->a
    Composit::Composit c(&b);    // c->b->a
    Composit::Composit d(&c);    // d->c->b->a

    f(d);
    f(c);
    f(b);
    f(a);

}
00DEF9D4: I am a composit!
00DEF9E4: I am a composit!
00DEF9F4: I am a composit!
00DEFA04: I am a leaf
00DEF9E4: I am a composit!
00DEF9F4: I am a composit!
00DEFA04: I am a leaf
00DEF9F4: I am a composit!
00DEFA04: I am a leaf
00DEFA04: I am a leaf
1 Answers

It's because Composit::Composit c(b) calls the copy constructor, which is generated by default. This is why the pointer version works, since a Composit* argument won't work with the copy constructor.

There's no good way to prevent this behaviour, since if Composit::Composit c(b) is valid, most containers/generic algorithms will expect this to be a copy (but it won't be, since it will just make c a wrapper around b). I would suggest a factory function to make Composit objects instead.

Related