简体   繁体   中英

Why can't I instantiate a reference to a base class at the same time as a pointer to a derived class?

The simplest example of my question can be seen in the following code snippet:

class Handle : public IHandle_<Handle>{
    public:
        Handle(std::unique_ptr<Derived> aDerived)
            : derived(std::move(aDerived)),
              base(*aDerived) {};

        std::unique_ptr<Derived> derived;
        Base& base;
};

Here, you can see that the Handle class is essentially a wrapper around Derived . More importantly, I wish to expose the base class of Derived , Base , by way of a reference. The reason for this is that, ultimately, I wish for Handle to look something like this:

class Handle : public IHandle_<Handle>{
    private:
        std::unique_ptr<Derived1> myD1;
        std::unique_ptr<Derived2> myD2;

    public:
        Handle(std::unique_ptr<Derived1> aD1)
            : myD1(std::move(aD1)),
              base1(*aD1),
              base2(*aD1){};
        Handle(std::unique_ptr<Derived2> aD2)
            : myD2(std::move(aD2)),
              base1(*aD2),
              base2(*aD2){};

        Base1& base1;
        Base2& base2;

};

The reason I wish for Handle to work like this is that I am using it as a 'Component' in an 'entity component system', and I'd like for this particular component to be instantiatable from two different concrete implementations of the same two base classes. I mention this because an 'entity component system' design pattern by definition departs from traditional object-oriented programming practices: in other words, I know there are other ways of accomplishing what I am trying to do, however I wish to make it work in some variation of what I have listed here.

Question

Why does the simple Handle example shown in my first snippet fail? It compiles fine but seg-faults when trying to access a method in Base . If I change the order in which I instantiate the member variables of Handle , I get some errors at compile time which I think could provide some hints but I do not really understand what is going on.

Here is a full working example of Handle and the classes it depends on:

#include <memory>
#include <iostream>

class Base{
    public:
        Base(int ax) : x(ax){};
        virtual ~Base() = 0;
        virtual void setVal(float a) = 0;
        virtual float getVal() = 0 ;

        int x;
};

Base::~Base(){}

class Derived : public Base{
    public:
        Derived(int ax, int az)
            : Base(ax), z(az){};

        int z;
};

class Concrete : public Derived{
    public:
        Concrete(int ax, int aw, int av)
            : Derived(ax, aw),
              v(av){};
        void setVal(float a) override{
            myVal = a;
        }
        float getVal() override{
            return myVal;
        }
        float myVal;
        int v;
};

class IHandle{
    public:
        virtual ~IHandle() = 0;
};

IHandle::~IHandle(){}

template <class T>
class IHandle_ : public IHandle{
    public:
        virtual ~IHandle_() = 0;
};

template <class T>
IHandle_<T>::~IHandle_(){};

class Handle : public IHandle_<Handle>{
    public:
        Handle(std::unique_ptr<Derived> aDerived)
            : derived(std::move(aDerived)),
              base(*aDerived) {};

        std::unique_ptr<Derived> derived;
        Base& base;
};


int main(){
    // These two pointers are owned by an EntityManager
    std::unique_ptr<Derived> ptr(new Concrete(1, 2, 3));

    // we can get a reference to an IHandle from the EntityManager
    std::unique_ptr<IHandle> aHandle(new Handle(std::move(ptr)));

    // We need, specifically, a `Handle` implementation of `IHandle`
    Handle& handle = static_cast<Handle&>(*aHandle);
    // seg fault on the following line
    handle.base.setVal(10.0);
    std::cout << "a = " << handle.base.getVal() << std::endl;
    return 0;
}

The members in a C++ class are initialized in the order you declare them, so looking at the first snippet, the order of initialization of the members in the Handle class is:

  • derived
  • base

That said, it means that in the constructor the line

derived(std::move(aDerived))

will transfer the internal resources of aDerived to derived , reasonably resetting the state of aDerived . So as soon as your code reach the statement

base(*aDerived)

base will reference an empty (depends on your move constructor implementation inside Base and Derived class) object that most likely will be deleted from memory after the call of the constructor itself.

So, I believe that any reference to base you got in your code are pointing to a not allocated memory, giving the SEG_FAULT error.

SEG_FAULT most of the time refers to code that is using (in your case writing, see setval() ) an area of memory not (yet or anymore) allocated for the running process.

Hope this may help, Have a good night, Stefano

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM