编辑以下决议,经过大量修改后,可以转发不相关的细节并解释实际问题。
我最近在一些旧代码中发现了一个问题。特定的代码很少使用,并且没有足够的单元测试(当我发现问题时我添加了更多)。我最近才停止使用Visual C ++ 2003,并且VC ++ 9中没有显示症状,仅在MinGW GCC 4.4.0中显示。并且只发布版本。一旦我添加了任何跟踪代码,症状就会消失。你不喜欢那些人吗?
无论如何,事实证明在所有情况下都没有调用复制构造函数和赋值运算符重载。结果,数据结构变得不一致。大部分时间我都在匆匆而过。
我通过VC ++ 9调试器逐步查看了这个问题,即使VC ++ 9版本没有显示出症状。所以,至少有两个编译器表现相同,即使症状没有表现出来 - 这是一个公平暗示,让我意识不到的行为符合标准,而且我一直依赖于旧的非标准行为。 / p>
那么......在什么情况下,继承基类的构造函数等无法被调用?
FWIW,问题代码是导致this question的库的一部分。
解决
有两个不同的问题 - 复制构造和重载分配。
复制构建问题结果证明是众所周知的return value optimisation issue, in the more recent "named" variant。涉及的对象是指向多路树叶节点的“游标”(如迭代器)。为了确保它们得到维护,每个叶节点都保留一个链接的游标列表。
游标是一个非常轻量级的类 - 叶节点指针,节点中的下标和维护列表的下一个指针。需要显式构造/销毁/分配的唯一原因是维护游标列表的副作用。
因为类是如此轻量级,并且因为很少有多个游标(特别是很少有一个或两个游标指向特定的叶节点),我有时会使用pass-by-value和return-by-值。后者似乎是问题所在。
更改复制构造函数实现意外地绕过了这个问题 - 我得到的印象(未经证实)对于发布版本,MinGW GCC和VC ++仍然试图避免绕过具有副作用的复制构造函数,但是会遗漏某些副作用
第二个问题 - 分配过载。我认为,这对我来说是一个古老的愚蠢错误,如果我在大致相同的时间没有复制构造函数问题,我会更快地解决它。
赋值重载是通过两层继承继承的,其中包含一些模板,隐私和依赖类型的复杂性。中间或派生类都没有超越它。在这种特殊情况下,这种方式是有意义的(以错误的方式) - 派生类不添加任何成员数据,它只是将模板参数提供给基础并添加一些方法。但如果标准有“特殊情况”,我会非常惊讶“但我没有添加任何成员数据”。
对于较旧的编译器,我认为我正在使用隐式转换规则,允许调用基类赋值操作。我不想试着追查行为改变的确切原因 - 关键是我因为编写一些杂乱而奇怪的结构化代码而受到惩罚。
答案 0 :(得分:8)
对于正确的C ++实现,这绝不会在正确的代码中发生。如果是这样,您可能有编译器错误,或者您的代码在对象构造之前已经进入未定义的行为区域,因为代码中存在错误。后者是最有可能的。
答案 1 :(得分:2)
最重要的情况是它完全跳过调用复制构造函数。即使在可观察行为发生变化的情况下,这也是一种特定的优化。
另一个抓住一些缺乏经验的开发人员的案例是,除非另有说明,否则Derived::Derived(T)
会调用Base::Base()
- 而不是Base::Base(T)
答案 2 :(得分:1)
从不。如果创建Derived类,则将始终调用Base类构造函数。如果您调用Derived析构函数(假设您记住了一个虚析构函数),那么也将调用Base析构函数。从构造函数/析构函数的角度来看,继承与组合的行为没有任何不同。你在上面看到的是一种椭圆形。你的编译器决定你永远不会对这个值做任何事情,并且摆脱它。
编辑:或者正如Neil所说,你首先发现了未定义的行为,这导致了这种情况的发生。