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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于C++基类、派生类的引用和指针相关的知识,希望对你有一定的参考价值。

参考技术A 基类的指针或者引用指向派生类的实例,这在面向对象编程中使用极其普遍。
A
*pA
=
new
B;这是一个基类指针指向一个派生类的实例。
B
b;
A
&rb=b;这是一个基类引用指向(引用)派生类的实例。
至于这个指针pA和引用rb的访问范围,完全由pA和rb定义所在的范围决定,跟它们所指向的目标无关。
通过基类指针或者引用来访问派生类实例的意义在于,这种指针和引用可以通用于访问这个基类之下的所有派生类的对象,这一方面可以使用面向对象的“多态”特性,通过这个基类指针或者引用来调用虚函数的时候,实际执行的是派生类对象的函数,使用这个指针或者引用的一方的代码不必随着派生类的不同而改变,却可以达到执行最适合这个派生类的函数(也就是这个派生类自己的成员函数)的目的;另一方面可以使程序模块具有很好的可替换性,用一个派生类替换另一个派生类,程序的其它部分不需要做任何改动就可以正常运行而且发挥出新的派生类的特性。
PS:基类指针和引用可以用来访问派生类对象是把派生类对象看成基类对象。理论基础是:一个派生类对象一定也是一个基类对象。

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

【中文标题】为啥我不能同时实例化对基类的引用作为派生类的指针?【英文标题】: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++基类、派生类的引用和指针的主要内容,如果未能解决你的问题,请参考以下文章

派生类和基类的转换

无法将参数 1 从派生指针转换为基类指针引用

向上强制转换和向下强制转换

通过基类指针C++访问派生类的成员

C++:友元函数,派生类

在 C++ 中将指向基类的指针传递给派生类的成员函数