【问题标题】:Is using a reference to a derived class member in its base class well defined in C++?在 C++ 中定义良好的基类中使用对派生类成员的引用吗?
【发布时间】:2021-10-14 08:22:59
【问题描述】:

我正在研究一些代码,它似乎可以工作,但我不确定它是否是已定义的行为。

我认为其中存在问题,因为 Base 是通过对 Derived 成员变量的引用构造的 - 在我的理解中,这是在 Base 之后构造的。

我对此进行了一些研究,我发现的所有答案都表明 Base 是在 Derived 之前构造的,并且来自 Derived 构造函数的参数可以转发给 Base 构造函数。

但是派生成员呢,它们可以安全地转发给基础构造函数吗? Base 中的构造函数和析构函数甚至成员函数是否可以处理无效对象?

这里是有问题的简单代码示例:

class Base
{
public:
   Base(SomeClass & obj): m_obj(obj)
   {
       // Does using m_obj here cause problems with a Derived instance?
   }

   virtual ~Base()
   {
       // Does using m_obj here cause problems with a Derived instance?
   }
   
   void SomeMethod()
   {
       // Does using m_obj here cause problems with a Derived instance?
   }
   
private:
   SomeClass & m_obj;
};


class Derived : public Base
{
public:
    Derived():Base(m_derObj){}

private:
    SomeClass m_derObj{123};
};

OnlineGDB

也许我错过了 C++ 提供的一些保证 - 或者我们只是幸运,错误从未发生过。

【问题讨论】:

  • 除了SomeClass m_derObj(123); 中的错误(这是一个无效的成员函数声明),没关系。
  • 很抱歉 - 修复它并添加到 OnlineGDB 的链接,它可以编译。感谢您的快速回答,但也许您可以帮我弄清楚为什么这不是问题,对我来说,在 Base-Constructor 运行之后似乎仍然构造了 SomeClass。
  • @Someprogrammerdude 当引用Base::m_obj被初始化的时候,被引用的对象Derived::m_derObj还没有被初始化,对吧?是否可以创建对尚未初始化的对象(基本上是其存储)的引用?
  • @DanielLangr 它尚未初始化,但它存在并且可以创建对它的引用。只要该对象不以任何其他方式使用,那么一切都很好。
  • @mile4712 Base构造函数可以初始化m_obj成员,但是构造函数体不能使用引用的对象,因为它没有被初始化。

标签: c++ oop undefined-behavior


【解决方案1】:

是否在 C++ 中定义良好的基类中使用了对派生类成员的引用?

是的。

SomeClass m_derObj(123);

这是无效的语法。如果您打算编写默认成员初始化程序,则必须使用花括号或等号初始化程序。

Base(SomeClass & obj): m_obj(obj)
{
    // Does using m_obj here cause problems with a Derived instance?
}

引用的对象尚未初始化,因此您可以对引用执行的操作非常有限。

如果您尝试执行此类引用不允许的操作,例如尝试访问引用的对象,则程序的行为是未定义的。如果你不这样做,那也没关系。

virtual ~Base()
{
    // Does using m_obj here cause problems with a Derived instance?
}

引用的对象已经被销毁了,所以你对引用的操作非常有限。

如果您尝试执行此类引用不允许的操作,例如尝试访问引用的对象,则程序的行为是未定义的。如果你不这样做,那也没关系。

void SomeMethod()
{
    // Does using m_obj here cause problems with a Derived instance?
}

根据您用于初始化对象的构造函数,引用可能会变得悬空。在这种情况下,您将无法对引用执行任何操作而不会导致未定义的行为。此外,如果您从构造函数或析构函数调用该函数,则会出现对象被销毁/尚未构造的问题。

但是在引用仍然有效的情况下,没关系。

【讨论】:

  • 对不起,我现在修复了原始代码中的语法错误。在最后一种情况下,该方法的引用怎么可能悬空?
  • @mile4712 引用的对象可能已被销毁。
  • 好的,我可以想到这种情况确实发生的情况,但仅限于多线程环境,或者在析构函数中调用该方法还是我错了?
  • @mile4712 想想使用复制构造函数时会发生什么。副本指的是什么对象?当原件被破坏时会发生什么?答案:发生悬空引用。
【解决方案2】:

是的,你的预感是正确的。

在对象被构造之前或被销毁之后使用它具有未定义的行为,最糟糕的未定义行为类型是看起来正常工作。
(你不能通过观察一个 C++ 程序做它应该做的事情来断定它没有未定义的行为。)

构造函数可以存储引用,但不能以任何方式使用被引用的对象,因为该对象的生命周期尚未开始。

析构函数不能使用对象,因为它的生命周期已经结束。

您可以在其生命周期内以常规方式在其他成员函数中使用引用的对象。

这个终身问题是避免作为成员引用的另一个原因。

【讨论】:

    猜你喜欢
    • 2016-05-21
    • 1970-01-01
    • 2012-09-17
    • 1970-01-01
    • 2012-01-03
    • 1970-01-01
    • 1970-01-01
    • 2022-01-10
    • 1970-01-01
    相关资源
    最近更新 更多