2

我很难调试生产中的崩溃。只是想与这里的人确认语义。我们有一个像...

class Test {
public:
  Test()
  {
    // members initialized ...
    m_str = m_str;
  }
  ~Test() {}
private:
  // other members ...
  std::string m_str;
};

有人将初始化更改为使用 ctor 初始化列表,这在我们的代码语义中是相当正确的。初始化的顺序和它们的初始值是正确的。所以这个类看起来像......

class Test {
public:
  Test() 
    : /*other inits ,,, */ m_str(m_str)
  {
  }
  ~Test() {}
private:
  // other members ...
  std::string m_str;
};

但是代码突然开始崩溃!我将一长串初始化列表与这段代码隔离开来m_str(m_str)。我通过链接文本确认了这一点。

它必须崩溃吗?标准对此有何评论?(这是未定义的行为吗?)

4

5 回答 5

15

第一个构造函数等价于

  Test()
  : m_str()
  {
    // members initialized ...
    m_str = m_str;
  }

也就是说,当您在构造函数中进行赋值时,m_str 已经隐式初始化为空字符串。因此,对 self 的赋值,虽然完全没有意义和多余,但不会导致任何问题(因为std::string::operator=(),正如任何写得很好的赋值运算符应该检查自赋值并且在这种情况下什么都不做)。

但是,在第二个构造函数中,您尝试m_str在初始化列表中使用自身进行初始化 - 此时它尚未初始化。所以结果是未定义的行为。

更新:对于原始类型,这仍然是未定义的行为(导致具有垃圾值的字段),但它不会崩溃(通常 - 请参阅下面的注释以获取异常),因为根据定义,原始类型没有构造函数、析构函数并且不包含指针到其他物体。

对于不包含具有所有权语义的指针成员的任何类型也是如此。std::string特此证明不是其中之一:-)

于 2010-10-08T15:50:40.947 回答
2

m_str在初始化列表中构造。因此,在您将其分配给自身时,它还没有完全构建。因此,未定义的行为。

(无论如何,那个自我分配应该做什么?)

于 2010-10-08T15:42:03.150 回答
2

原来通过赋值的“初始化”是完全多余的。

除了浪费处理器周期之外,它没有造成任何伤害,因为在分配时,默认情况下 m_str 成员已经被初始化。

在第二个代码片段中,默认初始化被覆盖以使用尚未初始化的成员来初始化自身。那是未定义的行为。这是完全没有必要的:只需删除它(不要重新引入最初的浪费时间,只需删除)。

通过调高编译器的警告级别,您可能会收到有关此代码和类似的琐碎不良代码的警告。

不幸的是,您遇到的问题不是这个技术问题,而是更根本的问题。这就像汽车厂的工人对他们为新汽车品牌安装的方形轮子提出问题。那么问题不在于方轮不起作用,而是很多工程师和管理人员都参与了使用外观精美的方轮的决定,但没有人反对——其中一些人无疑没有反对。不明白方轮不起作用,但我怀疑,他们中的大多数人只是害怕说出他们 100% 确定的事情。所以这很可能是一个管理问题。对不起,但我不知道如何解决这个问题......

于 2010-10-08T15:52:55.130 回答
1

未定义的行为不一定会导致崩溃——它几乎可以做任何事情,从好像完全没有问题一样继续工作,到立即崩溃,再到做一些非常奇怪的事情,后来导致看似无关的问题。规范的说法是它使“恶魔飞出你的鼻子”(又名,“导致鼻恶魔”)。曾几何时,该阶段的发明者有一个(非常酷的)网站,讲述了核战争始于某人在“DeathStation 9000”中引起未定义行为的情况。

编辑:标准的确切措辞是(§:1.3.12):

1.3.12 未定义的行为 [defns.undefined]

行为,例如在使用错误程序结构或错误数据时可能出现的行为,本国际标准对此没有要求。当本国际标准省略对任何明确的行为定义的描述时,也可能会出现未定义的行为。[注意:允许的未定义行为范围从完全忽略具有不可预测结果的情况,到在翻译或程序执行期间以环境特征的记录方式表现(有或没有发出诊断消息),到终止翻译或执行(发出诊断消息)。

于 2010-10-08T15:44:15.557 回答
0

这与之间的区别相同

std::string str;
str = str;

std::string str(str);

前者有效(尽管这是无稽之谈),后者无效,因为它试图从尚未构造的对象复制构造对象。

当然,要走的路是

Test() : m_str() {}
于 2010-10-08T15:50:24.743 回答