我很难调试生产崩溃。只是想与这里的人们确认语义。我们有一个类... ...
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)
。我通过link text确认了这一点。
是否必须崩溃?标准对此有何看法? (这是不确定的行为吗?)
答案 0 :(得分:13)
第一个构造函数等同于
Test()
: m_str()
{
// members initialized ...
m_str = m_str;
}
也就是说,当你在构造函数中进行赋值时,m_str
已经被隐式初始化为为空字符串。因此,对自我的赋值虽然完全没有意义且多余,但不会导致任何问题(因为std::string::operator=()
,因为任何编写良好的赋值运算符都应该检查自我赋值,并且在这种情况下不执行任何操作)。
但是,在第二个构造函数中,您尝试在初始化列表中初始化m_str
- 此时尚未初始化。所以结果是未定义的行为。
更新:对于原始类型,这仍然是未定义的行为(导致带有垃圾值的字段),但它不会崩溃(通常 - 请参阅下面的注释以了解异常),因为原始类型由定义没有构造函数,析构函数,也没有指向其他对象的指针。
对于任何不包含具有所有权语义的指针成员的类型,情况也是如此。特此证明std::string
不是其中之一: - )
答案 1 :(得分:2)
m_str
在初始化列表中构造。因此,在您将其分配给自身时,它并未完全构建。因此,未定义的行为。
(那个应该做的自我分配是什么?)
答案 2 :(得分:2)
分配的原始“初始化”完全是多余的。
除了浪费处理器周期之外,它没有造成任何伤害,因为在分配时m_str成员已默认初始化。在第二个代码段中,将覆盖默认初始化以使用尚未初始化的成员来初始化自身。这是未定义的行为。而且完全没必要:只需删除它(并且不要重新引入原始的时间浪费,只需删除)。
通过调高编译器的警告级别,您可能会收到有关此类和类似的虚拟代码的警告。
不幸的是,你遇到的问题不是技术问题,而是更为根本的问题。这就像汽车工厂的工人提出了一个关于他们正在推出新车品牌的方形车轮的问题。然后问题不在于方轮不起作用,而是许多工程师和管理人员参与决定使用花哨的方形车轮并且没有人反对 - 其中一些毫无疑问没有'我知道方形轮子不起作用,但我怀疑大部分都只是害怕说出他们100%肯定的东西。所以这很可能是一个管理问题。对不起,但我不知道解决这个问题......
答案 3 :(得分:1)
未定义的行为不一定会导致崩溃 - 它可以做任何事情,从继续工作,好像根本没有问题,立即崩溃,做一些非常奇怪的事情,导致看似无关的问题后来。规范主张是它使“恶魔飞出你的鼻子”(又名“引起鼻子恶魔”)。有一段时间,这个阶段的发明者有一个(非常酷的)网站,讲述了从“DeathStation 9000”中导致未定义行为的人开始的核战争。
编辑:标准的确切措辞是(§:1.3.12):
1.3.12未定义的行为[defns.undefined]
行为,例如在使用错误的程序构造或错误数据时可能出现的行为 国际标准没有要求。此时也可能会出现未定义的行为 国际标准省略了对行为的任何明确定义的描述。 [注意:允许未定义 行为的范围从完全忽略情况与不可预测的结果,到行为期间 以环境特征记录的方式进行翻译或程序执行(有或没有 发布诊断信息),终止翻译或执行(发布一个 诊断信息)。
答案 4 :(得分:0)
这与
之间的区别相同std::string str;
str = str;
和
std::string str(str);
前者的作品(尽管它是无意义的),后者却没有,因为它试图从尚未构造的对象中复制构造一个对象。
当然,要走的路是
Test() : m_str() {}