回答了 关于的问题 std:: 对象向量和 const 正确性, ,并收到有关未定义行为的评论。我不同意,因此我有一个问题。

考虑带有 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:

如果对象的寿命结束并在对象被重复使用或释放​​的存储之前转介到原始对象,或原始对象的名称将自动参考新对象,一旦新对象的寿命开始,就可以用来操纵新对象,如果:

...

- 原始对象的类型不合格,如果类型类型,则不包含任何类型为constemified或参考类型的非静态数据成员

现在,“在一个对象的生命周期结束之后,在该对象占用的存储被重用或释放之前,在原始对象占用的存储位置上创建一个新对象”正是您正在做的事情。

您的对象是类类型,并且它 包含类型为 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 对象的代码时,这种保证的潜在价值应该是显而易见的。有什么办法可以修改 x.c 没有 如果援引 UB,则必须取消该保证。所以,只要标准编写者没有错误地完成了他们的工作,就没有办法做你想做的事。

[*] 事实上我对使用有疑问 this 作为放置新的参数 - 可能你应该将其复制到 void* 首先,并使用了它。但我并不关心这是否是 UB,因为它不会保存整个函数。

其他提示

首先:当您制作数据成员时 const, ,您正在告诉编译器和全世界 该数据成员永远不会改变. 。当然 你不能分配 而且你当然 不能欺骗 不管技巧多么聪明,编译器都接受了这样做的代码。
你可以有一个 const 数据成员 或者 分配给所有数据成员的作业运算符。 你不能两者兼而有之。

至于您对问题的“解决方案”:
我想 在为该对象调用成员函数中的对象上调用破坏者 会调用 UB 马上。 在非初始化的原始数据上调用构造函数,以从成员函数中创建对象,该对象已被调用,该对象已驻留在现在在原始数据上调用构造函数的对象... 还 非常 听起来很多 UB 对我来说。 (地狱,只是拼写出来使我的脚趾甲卷曲。)不,我没有标准的章节和经文。我讨厌阅读标准。我想我不能忍受它的仪表。

但是,除了技术上,我承认您几乎可以在每个平台上使用“解决方案” 只要代码与您的示例一样简单. 。不过,这并不能使 好的 解决方案。实际上,我认为这甚至不是 可以接受 解决方案,因为IME代码永远不会像那样简单。多年来,它将扩展,更改,变异和扭曲,然后默默失败,需要令人难忘的36小时调试转移,以便找到问题。我不认识你,但是每当我找到这样的代码时,有36小时的调试乐趣,我想扼杀对我这样做的悲惨的愚蠢态度。

草药萨特(Herb Sutter) Gotw#23, ,逐一剖析这个想法,最后得出结论: 充满陷阱, , 它的 经常错, ,然后 使生活成为派生班的作者的生命地狱... 切勿使用明确的破坏者,然后再安置新的秘诀,以复制构造实现复制分配, ,即使这个捣蛋每三个月就新闻组进行一次,”(强调我的)。

如果有const成员,您如何将其分配给A?您正在尝试完成根本上不可能的事情。您的解决方案对原始行为没有新的行为,这不一定是UB,但绝对是您的。

一个简单的事实是,您正在更改const成员。您要么需要拆开会员,要么抛弃分配操作员。没有解决问题的方法 - 这是完全矛盾的。

为了更清楚地编辑:

const演员并不总是引入不确定的行为。但是,您肯定会做到。除其他任何东西之外,不确定的是不打电话给所有驱动器 - 除非您确定T是POD类,否则您甚至都没有将正确的人调用。此外,还有各种形式的继承涉及的野性时间不确定的行为。

您确实调用了不确定的行为,您可以避免这种行为 不试图分配给const对象。

如果您绝对想拥有一个不变的(但可分配)的成员,那么如果没有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';
}

阅读此链接:

http://www.informit.com/guides/content.aspx?g=cplusplus&seqnum = 368

尤其...

据称,此技巧可防止代码重复。但是,它有一些严重的缺陷。为了工作,C的驱动器必须分配已删除的每个指针,因为随后的复制构造函数调用可能会在将新值重新分配到char数组时再次删除相同的指针。

在没有其他的情况下(非const)成员,这根本没有任何意义,无论是否有未定义的行为。

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

afaik,这不是在这里发生的不确定行为,因为 c 不是一个 static const 实例,或者您无法调用复制分配操作员。然而, const_cast 应该敲响铃铛,告诉你一些问题。 const_cast 主要设计用于围绕非 const- 正确的API,这里似乎并非如此。

另外,在以下片段中:

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

你有 两个主要风险, ,其中的第一个已经指出。

  1. 存在 两个都 一个派生类的实例 A 一个虚拟驱动器,这将仅导致原始实例的部分重建。
  2. 如果构造函数呼叫 new(this) A(right); 抛出例外,您的对象将被销毁两次。在这种特殊情况下,这不会是问题,但是如果您碰巧清理大量清理,您将后悔。

编辑: :如果您的班级有这个 const 在您的对象中不考虑“状态”的成员(即,它是用于跟踪实例的某种ID,而不是比较的一部分 operator== 等等),然后以下内容可能是有道理的:

A& operator=(const A& assign) 
{ 
    // Copy all but `const` member `c`.
    // ...

    return *this;
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top