C++ const 成员和赋值运算符。如何避免未定义的行为?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4136156/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
const member and assignment operator. How to avoid the undefined behavior?
提问by Alexey Malistov
I answeredthe question about std::vector of objects and const-correctness, and received a comment about undefined behavior. I do not agree and therefore I have a question.
我回答了关于对象的 std::vector 和 const-correctness 的问题,并收到了关于未定义行为的评论。我不同意,因此我有一个问题。
Consider the class with const member:
考虑具有 const 成员的类:
class A {
public:
const int c; // must not be modified!
A(int c) : c(c) {}
A(const A& copy) : c(copy.c) { }
// No assignment operator
};
I want to have an assignment operator but I do not want to use const_cast
like in the following code from one of the answers:
我想要一个赋值运算符,但我不想const_cast
在以下答案之一的代码中使用:
A& operator=(const A& assign)
{
*const_cast<int*> (&c)= assign.c; // very very bad, IMHO, it is undefined behavior
return *this;
}
My solution is
我的解决方案是
A& operator=(const A& right)
{
if (this == &right) return *this;
this->~A()
new (this) A(right);
return *this;
}
Do I have undefined behavior (UB)?
我有未定义的行为 (UB) 吗?
What would be a solution without UB?
没有 UB 的解决方案是什么?
回答by Steve Jessop
Your code causes undefined behavior.
您的代码会导致未定义的行为。
Not just "undefined if A is used as a base class and this, that or the other". Actually undefined, always. return *this
is already UB, because this
is not guaranteed to refer to the new object.
不只是“未定义如果 A 用作基类以及这个、那个或另一个”。实际上未定义,总是。return *this
已经是UB,因为this
不能保证引用新对象。
Specifically, consider 3.8/7:
具体来说,考虑 3.8/7:
If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:
...
— the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type,
如果在一个对象的生命周期结束后,在该对象所占用的存储空间被重用或释放之前,在原对象所占用的存储位置创建一个新对象,一个指向原对象的指针,一个指向该对象的引用引用原始对象,或者原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,可用于操作新对象,如果:
...
— 原始对象的类型不是 const 限定的,并且,如果类类型不包含任何类型为 const 限定或引用类型的非静态数据成员,
Now, "after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied" is exactly what you are doing.
现在,“在对象的生命周期结束后,在对象占用的存储空间被重用或释放之前,在原始对象占用的存储位置创建一个新对象”正是您正在做的。
Your object is of class type, and it doescontain a non-static data member whose type is const-qualified. Therefore, after your assignment operator has run, pointers, references and names referring to the old object are notguaranteed to refer to the new object and to be usable to manipulate it.
您的对象属于类类型,并且它确实包含一个类型为 const 限定的非静态数据成员。因此,在您的赋值运算符运行后,不能保证引用旧对象的指针、引用和名称会引用新对象并可用于操作它。
As a concrete example of what might go wrong, consider:
作为可能出错的具体示例,请考虑:
A x(1);
B y(2);
std::cout << x.c << "\n";
x = y;
std::cout << x.c << "\n";
Expect this output?
期待这个输出?
1
2
Wrong! It's plausible you might get that output, but the reason const members are an exception to the rule stated in 3.8/7, is so that the compiler can treat x.c
as the const object that it claims to be. In other words, the compiler is allowed to treat this code as if it was:
错误的!您可能会得到该输出是合理的,但 const 成员是 3.8/7 中规定的规则的例外的原因是,编译器可以将其x.c
视为它声称的 const 对象。换句话说,允许编译器将此代码视为:
A x(1);
B y(2);
int tmp = x.c
std::cout << tmp << "\n";
x = y;
std::cout << tmp << "\n";
Because (informally) const objects do not change their values. The potential value of this guarantee when optimizing code involving const objects should be obvious. For there to be any way to modify x.c
withoutinvoking UB, this guarantee would have to be removed. So, as long as the standard writers have done their job without errors, there is no way to do what you want.
因为(非正式地)const 对象不会改变它们的值。在优化涉及 const 对象的代码时,这种保证的潜在价值应该是显而易见的。为了在x.c
不调用 UB 的情况下进行修改,必须删除此保证。所以,只要标准作家没有错误地完成他们的工作,就没有办法做你想做的事。
[*] In fact I have my doubts about using this
as the argument to placement new - possibly you should have copied it to a void*
first, and used that. But I'm not bothered whether that specifically is UB, since it wouldn't save the function as a whole.
[*] 事实上,我对this
用作放置 new 的参数表示怀疑- 可能你应该将它复制到void*
第一个,然后使用它。但我并不担心这是否是 UB,因为它不会将功能保存为一个整体。
回答by sbi
First: When you make a data member const
, you're telling the compiler and all the world that this data member never changes. Of course then you cannot assign to itand you certainly must not trickthe compiler into accepting code that does so, no matter how clever the trick.
You can either have a const
data member oran assignment operator assigning to all data members. You can't have both.
第一:当你创建一个数据成员时const
,你是在告诉编译器和全世界这个数据成员永远不会改变。当然,你不能分配给它,你当然不能欺骗编译器接受这样做的代码,无论这个技巧多么聪明。
您可以将const
数据成员或赋值运算符分配给所有数据成员。你不能两者兼得。
As for your "solution" to the problem:
I suppose that calling the destructor on an object within a member function invoked for that objectswould invoke UBright away. Invoking a constructor on uninitialized raw data to create an object from within a member function that's been invoked for an object that resided where now the constructor is invoked on raw data... also verymuch sounds like UBto me. (Hell, just spelling this out makes my toenails curl.) And, no, I don't have chapter and verse of the standard for that. I hate reading the standard. I think I can't stand its meter.
至于您对问题的“解决方案”:
我想在为该对象调用的成员函数内的对象上调用析构函数会立即调用UB。未初始化的原始数据调用构造函数,这就是被调用的地方,现在的构造是在原始数据调用的居住对象的成员函数中创建一个对象...也是非常多的声音就像UB给我。(见鬼,只是拼写出来会让我的脚趾甲卷曲。)而且,不,我没有标准的章节和经文。我讨厌阅读标准。我想我受不了它的仪表。
However, technicalities aside, I admit that you might get away with your "solution" on just about every platform as long as the code stays as simple as in your example. Still, this doesn't make it a goodsolution. In fact, I'd argue it's not even an acceptablesolution, because IME code never stays as simple as that. Over the years it will get extended, changed, mutated, and twisted and then it will silently fail and require a mind-numbing 36hrs shift of debugging in order to find the problem. I don't know about you, but whenever I find a piece of code like this responsible for 36hrs of debugging fun I want to strangle the miserable dumb-wit who did this to me.
然而,撇开技术问题不谈,我承认,只要代码保持与示例中一样简单,您就可以在几乎所有平台上使用“解决方案” 。尽管如此,这并不是一个好的解决方案。事实上,我认为这甚至不是一个可以接受的解决方案,因为 IME 代码从来没有这么简单。多年来,它会得到扩展、改变、变异和扭曲,然后它会默默地失败,需要进行 36 小时的令人麻木的调试轮班才能找到问题。我不了解你,但每当我发现一段这样的代码负责 36 小时的调试乐趣时,我想扼杀对我这样做的可悲的笨蛋。
Herb Sutter, in his GotW #23, dissects this idea piece by piece and finally concludes that it "is full of pitfalls, it's often wrong, and it makes life a living hell for the authors of derived classes... never use the trick of implementing copy assignment in terms of copy construction by using an explicit destructor followed by placement new, even though this trick crops up every three months on the newsgroups" (emphasize mine).
Herb Sutter 在他的GotW #23中逐条剖析了这个想法,最后得出结论:它“充满陷阱,经常是错误的,它让派生类的作者们的生活变成了地狱……永远不要使用这个技巧通过使用显式析构函数和放置 new 来实现副本分配方面的副本分配,即使这个技巧每三个月在新闻组中出现一次”(强调我的)。
回答by Puppy
How can you possibly assign to an A if it has a const member? You're trying to accomplish something that's fundamentally impossible. Your solution has no new behaviour over the original, which is not necessarily UB but yours most definitely is.
如果 A 有一个 const 成员,你怎么可能分配给它?你试图完成一些根本不可能的事情。您的解决方案与原始解决方案相比没有新的行为,这不一定是 UB,但绝对是您的解决方案。
The simple fact is, you're changing a const member. You either need to un-const your member, or ditch the assignment operator. There is no solution to your problem- it's a total contradiction.
简单的事实是,您正在更改 const 成员。你要么需要取消你的成员,要么放弃赋值运算符。您的问题没有解决方案 - 这是一个完全矛盾的问题。
Edit for more clarity:
编辑更清晰:
Const cast does not always introduce undefined behaviour. You, however, most certainly did. Apart from anything else, it is undefined not to call all destructors- and you didn't even call the right one- before you placed into it unless you knew for certain that T is a POD class. In addition, there's owch-time undefined behaviours involved with various forms of inheritance.
Const cast 并不总是引入未定义的行为。然而,你肯定做到了。除此之外,除非您确定 T 是一个 POD 类,否则在放入它之前不调用所有析构函数是未定义的 - 而且您甚至没有调用正确的析构函数。此外,还存在与各种形式的继承有关的有时未定义的行为。
You do invoke undefined behaviour, and you can avoid this by not trying to assign to a const object.
您确实会调用未定义的行为,并且可以通过不尝试分配给 const 对象来避免这种情况。
回答by UncleBens
If you definitely want to have an immutable (but assignable) member, then without UB you can lay things out like this:
如果你肯定想要一个不可变(但可分配)的成员,那么没有 UB 你可以这样布置:
#include <iostream>
class ConstC
{
int c;
protected:
ConstC(int n): c(n) {}
int get() const { return c; }
};
class A: private ConstC
{
public:
A(int n): ConstC(n) {}
friend std::ostream& operator<< (std::ostream& os, const A& a)
{
return os << a.get();
}
};
int main()
{
A first(10);
A second(20);
std::cout << first << ' ' << second << '\n';
first = second;
std::cout << first << ' ' << second << '\n';
}
回答by Roddy
Have a read of this link:
阅读此链接:
http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=368
http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=368
In particular...
特别是...
This trick allegedly prevents code reduplication. However, it has some serious flaws. In order to work, C's destructor must assign NULLify every pointer that it has deleted because the subsequent copy constructor call might delete the same pointers again when it reassigns a new value to char arrays.
据称,这个技巧可以防止代码重复。然而,它有一些严重的缺陷。为了工作,C 的析构函数必须为它已删除的每个指针分配 NULLify,因为后续的复制构造函数调用可能会在将新值重新分配给 char 数组时再次删除相同的指针。
回答by André Caron
In absence of other (non-const
) members, this doesn't make any sense at all, regardless of undefined behavior or not.
在没有其他(非const
)成员的情况下,这根本没有任何意义,无论是否存在未定义的行为。
A& operator=(const A& assign)
{
*const_cast<int*> (&c)= assign.c; // very very bad, IMHO, it is UB
return *this;
}
AFAIK, this is no undefined behavior happening here because c
is not a static const
instance, or you couldn't invoke the copy-assignment operator. However, const_cast
should ring a bell and tell you something is wrong. const_cast
was primarily designed to work around non const
-correct APIs, and it doesn't seem to be the case here.
AFAIK,这不是这里发生的未定义行为,因为c
它不是static const
实例,或者您无法调用复制赋值运算符。但是,const_cast
应该按响铃并告诉您有问题。 const_cast
主要旨在解决不const
正确的 API,但这里似乎并非如此。
Also, in the following snippet:
此外,在以下代码段中:
A& operator=(const A& right)
{
if (this == &right) return *this;
this->~A()
new (this) A(right);
return *this;
}
You have two major risks, the 1st of which has already been pointed out.
你有两个主要的风险,第一个已经被指出。
- In presence of bothan instance of derived class of
A
anda virtual destructor, this will lead to only partial reconstruction of the original instance. - If the constructor call in
new(this) A(right);
throws an exception, your object will be destroyed twice. In this particular case, it won't be a problem, but if you happen to have significant cleanup, you're going to regret it.
- 在存在两个派生类的一个实例
A
和虚析构函数,这将导致只有原始实例的部分重建。 - 如果构造函数调用
new(this) A(right);
抛出异常,您的对象将被销毁两次。在这种特殊情况下,这不会成为问题,但如果您碰巧进行了重大清理,您会后悔的。
Edit: if your class has this const
member that is not considered "state" in your object (i.e. it is some sort of ID used for tracking instances and is not part of comparisons in operator==
and the like), then the following might make sense:
编辑:如果您的类有这个const
在您的对象中不被视为“状态”的成员(即它是用于跟踪实例的某种 ID,而不是比较的一部分operator==
等),那么以下内容可能有意义:
A& operator=(const A& assign)
{
// Copy all but `const` member `c`.
// ...
return *this;
}