const成员和赋值运算符.如何避免未定义行为? [英] const member and assignment operator. How to avoid the undefined behavior?

查看:131
本文介绍了const成员和赋值运算符.如何避免未定义行为?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

回答了关于std::vector of objects and const-correctness ,并收到有关未定义行为的评论.我不同意,因此我有一个问题.

考虑使用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
}; 

我想拥有一个赋值运算符,但是我不想像其中一个答案中的以下代码那样使用const_cast:

A& operator=(const A& assign) 
{ 
    *const_cast<int*> (&c)= assign.c;  // very very bad, IMHO, it is undefined behavior
    return *this; 
} 

我的解决方法是

A& operator=(const A& right)  
{  
    if (this == &right) return *this;  
    this->~A() 
    new (this) A(right); 
    return *this;  
}  

我是否有未定义的行为(UB)?

没有UB的解决方案是什么?

解决方案

您的代码会导致未定义的行为.

不仅如果将A用作基类,而这个,那个或另一个用作基类,则未定义".实际上始终是未定义的. return *this已经是UB,因为不能保证this引用新对象.

具体来说,考虑3.8/7:

如果在对象的生存期之后 已经结束,并且在存储之前 占用的对象被重用或 释放后,将在以下位置创建一个新对象 存放位置 原始对象被占用,一个指针 指向原始对象 提到的参考 原始对象或名称 原始对象会自动 引用新对象,一旦 新对象的生命周期具有 开始,可以用来操纵 新对象,如果:

...

-原始对象的类型是 没有const限定,如果是一类 类型,不包含任何非静态 类型为的数据成员 const限定或引用类型,

现在,在对象的生存期结束之后,在重新使用或释放​​该对象所占用的存储之前,将在原始对象所占用的存储位置创建一个新对象"./p>

您的对象属于类类型,并且确实包含类型为const限定的非静态数据成员.因此,在您的赋值运算符运行后, 不能保证引用旧对象的指针,引用和名称能够引用新对象并可以对其进行操作.

作为可能出问题的具体示例,请考虑:

A x(1);
B y(2);
std::cout << x.c << "\n";
x = y;
std::cout << x.c << "\n";

期望此输出?

1
2

错了!您可能会得到该输出,但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";

因为(非正式地) const对象不会更改其值.当优化涉及const对象的代码时,这种保证的潜在价值应该是显而易见的.为了能够在没有调用UB的情况下修改x.c 的任何方法,必须取消此保证.因此,只要标准编写者在没有错误的情况下完成他们的工作,就无法做您想要的事情.

[*]实际上,我对使用this作为放置new的参数感到怀疑-可能您应该首先将其复制到void*并使用它.但是我并不为它具体是UB而烦恼,因为它不会将功能整体保存起来.

I answered the 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.

Consider the class with const member:

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:

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)?

What would be a solution without UB?

解决方案

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.

Specifically, consider 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,

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 does contain 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 not guaranteed to refer to the new object and to be usable to manipulate it.

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:

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 without invoking 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.

[*] 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.

这篇关于const成员和赋值运算符.如何避免未定义行为?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆