代码之家  ›  专栏  ›  技术社区  ›  Steve314

在什么情况下C++将无法调用继承类的构造函数?

c++
  •  2
  • Steve314  · 技术社区  · 15 年前

    编辑 在解决之后,已经对其进行了实质性的编辑,以转储不相关的细节并解释实际问题。

    我最近在一些旧代码中发现了一个问题。这个特定的代码很少使用,并且没有足够的单元测试(当我发现这个问题时,我正在添加更多的代码)。我只是比较最近停止使用Visual C++ 2003,症状没有显示在VC++ 9中,只有在明文GCC 4.4.0。并且只发布构建。我一添加任何跟踪代码,症状就消失了。你不就是讨厌这些吗?

    无论如何,结果发现复制构造函数和赋值运算符重载并非在所有情况下都被调用。结果,数据结构变得不一致。大部分时间我都是侥幸通过的。

    我在VC++9调试器中逐步发现了这个问题,尽管VC++9构建没有显示出这些症状。因此,至少有两个编译器的行为是相同的,即使症状一个也没有显示出来——一个很好的暗示是,我不知道的行为是符合标准的,而我一直依赖于旧的非标准行为。

    所以…在什么情况下,继承的基类的构造函数等将无法调用?

    fwiw,问题代码是导致 this question .

    分辨率

    有两个独立的问题-复制构造和重载分配。

    复印件建设问题是众所周知的。 return value optimisation issue, in the more recent "named" variant . 涉及的对象是指向多路树叶节点的“光标”(如迭代器)。为了确保它们得到维护,每个叶节点都保留一个引用它的游标的链接列表。

    光标是一个非常轻量级的类——一个叶节点指针、节点内的一个下标,以及维护列表的下一个指针。需要显式构造/销毁/分配的唯一原因是维护光标列表的副作用。

    因为类非常轻,而且很少有超过几个游标(尤其是很少有超过一个或两个指针指向特定的叶节点),所以有时我使用传递值和按值返回。后者似乎是问题所在。

    更改复制构造函数的实现意外地绕过了这个问题-我得到的印象(未确认)是,对于发布版本,mingw gcc和vc++仍然试图避免绕过具有副作用的复制构造函数,但忽略了一些副作用。

    第二个问题-分配过载。我认为,这是我的一个愚蠢的老错误,如果我在大致相同的时间没有遇到复制构造函数的问题,我会更快地解决它。

    分配重载是通过两个继承层继承的,一路伴随着一些模板、隐私和依赖类型的复杂性。中间类和派生类都没有超越它。在这种特殊情况下,这种方法是有意义的(无论如何都是错误的),派生类不添加任何成员数据,它只向基提供模板参数并添加一些方法。但如果标准中有“但我没有添加任何成员数据”的特殊情况,我会非常惊讶。

    对于较旧的编译器,我认为我正在通过隐式强制转换规则,允许调用基类分配操作。我不想追查行为改变的确切原因——关键是我因为写了一些混乱的、结构怪异的代码而受到惩罚。

    3 回复  |  直到 12 年前
        1
  •  2
  •   MSalters    15 年前

    最重要的情况是,当它完全跳过调用复制构造函数时。这是一种特定的优化,即使在可观察行为发生变化的情况下也是允许的。

    另一个吸引一些缺乏经验的开发人员的例子是,除非另有规定, Derived::Derived(T) 电话 Base::Base() -不 Base::Base(T)

        2
  •  8
  •   anon    15 年前

    对于正确的C++实现,这将永远不会发生在正确的代码中。如果是这样的话,你要么有一个编译器的bug,要么你的代码在对象构造之前已经进入了未定义的行为领域,因为你的代码中有bug。后者是最有可能的。

        3
  •  1
  •   Puppy    15 年前

    从未。如果创建一个派生类,那么将始终调用基类构造函数。如果调用派生的析构函数(假设您记住了一个虚拟析构函数),那么也将调用基析构函数。从构造函数/析构函数的角度来看,继承的行为与组合没有任何不同。你可能在上面看到的是一种埃利斯语。你的编译器决定你永远不要用这个值做任何事情,然后去掉它。

    编辑:或者正如尼尔所说,你首先发现了未定义的行为,它导致了这种情况的发生。