Ctor Initializer:自初始化导致崩溃?
我很难调试生产中的崩溃。只是想与这里的人们确认语义。我们有一个像这样的类...
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;
};
但是代码突然开始崩溃!我将长长的 init 列表与这段代码 m_str(m_str)
隔离开来。我通过链接文本确认了这一点。
难道一定要崩溃吗?标准对此有何规定? (这是未定义的行为吗?)
I had a hard time debugging a crash on production. Just wanted to confirm with folks here about the semantics. We have a class like ...
class Test {
public:
Test()
{
// members initialized ...
m_str = m_str;
}
~Test() {}
private:
// other members ...
std::string m_str;
};
Someone changed the initialization to use ctor initialization-lists which is reasonably correct within our code semantics. The order of initialization and their initial value is correct among other things. So the class looks like ...
class Test {
public:
Test()
: /*other inits ,,, */ m_str(m_str)
{
}
~Test() {}
private:
// other members ...
std::string m_str;
};
But the code suddenly started crashing! I isolated the long list of inits to this piece of code m_str(m_str)
. I confirmed this via link text.
Does it have to crash? What does the standard say about this? (Is it undefined behavior?)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
第一个构造函数相当于
,当您在构造函数中进行赋值时,
m_str
已经被隐式初始化为一个空字符串。因此,对 self 的赋值虽然完全没有意义且多余,但不会导致任何问题(因为std::string::operator=()
,正如任何编写良好的赋值运算符应该检查的 self 赋值并且不执行任何操作在这种情况下)。但是,在第二个构造函数中,您尝试在初始化程序列表中使用其自身来初始化
m_str
- 此时它尚未初始化。所以结果是未定义的行为。更新:对于原始类型,这仍然是未定义的行为(导致字段具有垃圾值),但它不会崩溃(通常 - 请参阅下面的注释以了解异常),因为根据定义,原始类型没有构造函数、析构函数不包含指向其他对象的指针。
对于任何不包含具有所有权语义的指针成员的类型也是如此。
std::string
特此证明不是其中之一:-)The first constructor is equivalent to
that is, by the time you get to the assignment within the constructor,
m_str
has already been implicitly initialized to an empty string. So the assignment to self, although completely meaningless and superfluous, causes no problems (sincestd::string::operator=()
, as any well written assignment operator should, checks for self assignment and does nothing in this case).However, in the second constructor, you are trying to initialize
m_str
with itself in the initializer list - at which point it is not yet initialized. So the result is undefined behaviour.Update: For primitive types, this is still undefined behaviour (resulting in a field with garbage value), but it does not crash (usually - see the comments below for exceptions) because primitive types by definition have no constructors, destructors and contain no pointers to other objects.
Same is true for any type that does not contain pointer members with ownership semantics.
std::string
is hereby demonstrated to be not one of these :-)m_str
在初始化列表中构建。因此,当您将其分配给自身时,它尚未完全构建。因此,存在未定义的行为。(这个自我分配到底应该做什么?)
m_str
is constructed in the initialization list. Therefore, at the time you are assigning it to itself, it is not fully constructed. Hence, undefined behavior.(What is that self-assignment supposed to do anyway?)
原来通过赋值的“初始化”是完全多余的。
除了浪费处理器周期之外,它没有造成任何损害,因为默认情况下,在赋值时 m_str 成员已经被初始化。
在第二个代码片段中,默认初始化被重写,以使用尚未初始化的成员来初始化自身。这是未定义的行为。这是完全没有必要的:只需删除它(并且不要重新引入原来的浪费时间的内容,只需删除即可)。
通过调高编译器的警告级别,您可能会收到有关此代码和类似的简单不良代码的警告。
不幸的是,您遇到的问题不是技术问题,而是更为根本的问题。这就像汽车工厂的工人对他们在新汽车品牌上安装的方形车轮提出了疑问。那么问题不在于方形轮子不起作用,而是很多工程师和经理都参与了使用外观精美的方形轮子的决定,并且没有人反对 - 其中一些人无疑没有反对。我不知道方轮不起作用,但我怀疑,他们中的大多数人只是害怕说出他们百分百确定的事情。所以这很可能是管理问题。抱歉,但我不知道解决办法...
The original "initialization" by assignment is completely superfluous.
It didn't do any harm, other than wasting processor cycles, because at the time of the assignment the m_str member had already been initialized, by default.
In the second code snippet the default initialization is overridden to use the as-yet-uninitialized member to initialize itself. That's Undefined Behavior. And it's completely unnecessary: just remove that (and don't re-introduce the original time-waster, just, remove).
By turning up the warning level of your compiler you may be able to get warnings about this and similar trivially ungood code.
Unfortunately the problem you're having is not this technical one, it's much more fundamental. It's like a worker in a car factory poses a question about the square wheels they're putting on the new car brand. Then the problem isn't that the square wheels don't work, it's that a whole lot of engineers and managers have been involved in the decision to use the fancy looking square wheels and none of them objected -- some of them undoubtedly didn't understand that square wheels don't work, but most of them, I suspect, were simply afraid to say what that they were 100% sure of. So it's most probably a management problem. I'm sorry, but I don't know a fix for that...
未定义的行为不一定会导致崩溃——它几乎可以做任何事情,从继续工作,就好像根本没有问题一样,到立即崩溃,到做一些非常奇怪的事情,但后来会导致看似不相关的问题。规范的说法是它会让“恶魔从你的鼻子里飞出来”(又名“导致鼻恶魔”)。曾经,该相位的发明者有一个(非常酷的)网站,讲述了由某人在“DeathStation 9000”中引起未定义行为而引发的核战争。
编辑:标准中的确切措辞是(§:1.3.12):
1.3.12未定义行为[defns.undefined]
Undefined behavior doesn't have to lead to a crash -- it can do just about anything, from continuing to work as if there was no problem at all, to crashing immediately, to doing something really strange that causes seemingly unrelated problems later. The canonical claim is that it makes "demons fly out of your nose" (aka, "causes nasal demons"). At one time the inventor of the phase had a (pretty cool) web site telling about the nuclear war that started from somebody causing undefined behavior in the "DeathStation 9000".
Edit: The exact wording from the standard is (§:1.3.12):
1.3.12 undefined behavior [defns.undefined]
之间的区别相同。
这与和
前者有效(尽管这是无意义的),后者则不然,因为它尝试从尚未构造的对象复制构造对象。
当然,要走的路是
This is the same difference as between
and
The former works (although it's nonsense), the latter doesn't, since it tries to copy-construct an object from a not-yet-constructed object.
Of course, the way to go would be