为啥我不能同时实例化对基类的引用作为派生类的指针?

Posted

技术标签:

【中文标题】为啥我不能同时实例化对基类的引用作为派生类的指针?【英文标题】:Why can't I instantiate a reference to a base class at the same time as a pointer to a derived class?为什么我不能同时实例化对基类的引用作为派生类的指针? 【发布时间】:2017-08-18 02:55:45 【问题描述】:

我的问题最简单的例子可以看下面的代码sn-p:

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;
;

在这里,您可以看到Handle 类本质上是Derived 的包装器。更重要的是,我希望通过引用的方式公开DerivedBase 的基类。这样做的原因是,最终,我希望Handle 看起来像这样:

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;

;

我希望Handle 像这样工作的原因是我将它用作“实体组件系统”中的“组件”,并且我希望这个特定的组件可以从两个不同的具体实例中实例化相同的两个基类的实现。我提到这一点是因为根据定义,“实体组件系统”设计模式背离了传统的面向对象编程实践:换句话说,我知道还有其他方法可以完成我正在尝试做的事情,但是我希望让它在我在这里列出的一些变化。

问题

为什么我的第一个 sn-p 中显示的简单 Handle 示例会失败?尝试访问Base 中的方法时,它编译得很好,但会出现段错误。如果我更改实例化 Handle 的成员变量的顺序,我会在编译时遇到一些错误,我认为这可以提供一些提示,但我并不真正理解发生了什么。

这是Handle 及其依赖的类的完整工作示例:

#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;

【问题讨论】:

【参考方案1】:

C++类中的成员是按照你声明的顺序初始化的,所以看第一个sn-p,Handle类中成员的初始化顺序是:

派生 基础

也就是说,这意味着在构造函数中的行

derived(std::move(aDerived))

aDerived的内部资源转移到derived,合理重置aDerived的状态。因此,只要您的代码到达语句

base(*aDerived)

base 将引用一个 empty(取决于 Base 和 Derived 类中的移动构造函数实现)对象,该对象很可能会在调用构造函数本身后从内存中删除。

所以,我相信您在代码中对base 的任何引用都指向未分配的内存,从而导致 SEG_FAULT 错误。

SEG_FAULT 大多数时候是指正在使用(在您的情况下编写,请参见 setval() )未(尚未或不再)为正在运行的进程分配的内存区域的代码。

希望这可能会有所帮助, 祝你晚安, 斯特凡诺

【讨论】:

如果这个答案包含对 OP 问题的解决方案,将会得到改进。 我想我提供了一个“为什么”。但是按照您的评论,绝对合理:@wesanyer,基类引用有什么需要?为什么在你的上下文中需要它?

以上是关于为啥我不能同时实例化对基类的引用作为派生类的指针?的主要内容,如果未能解决你的问题,请参考以下文章

关于C++基类、派生类的引用和指针

通过基类的指针或引用调用虚函数构成多态

通过基类的指针或引用调用虚函数构成多态

继承和派生

关于C++基类与派生类

C++ 派生模板类:访问实例的受保护成员