代码之家  ›  专栏  ›  技术社区  ›  Alexey Malistov

常量成员和赋值运算符。如何避免未定义的行为?

  •  33
  • Alexey Malistov  · 技术社区  · 14 年前

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

    6 回复  |  直到 5 年前
        1
  •  39
  •   Steve Jessop    14 年前

    您的代码导致未定义的行为。

    return *this 已经是UB了,因为 this 不能保证引用新对象。

    如果,在一个对象的生命周期之后 已经结束并且在存储之前 所占用的对象被重用或 存储位置 原始对象被占用,指针 指的是原始物体 指的是 原始对象,或 原始对象将自动 引用新对象,一旦 新对象的生存期 开始,可以用来操纵 新对象,如果:

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

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

    你的对象是类类型的,它 包含类型为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

    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时,必须删除此保证。所以,只要标准的作者没有错误地完成了他们的工作,就没有办法做你想做的事情。

    实际上我对使用 作为placement new的参数-可能您应该将它复制到 void*

        2
  •  23
  •   sbi    14 年前

    第一:当您创建数据成员时 const 此数据成员从不更改 不能分配给它 你当然 编译器接受这样做的代码,无论技巧多么巧妙。
    常量 数据成员 分配给所有数据成员的赋值运算符。 你不能两者都有。

    至于你对这个问题的“解决方案”:
    我想是吧 会调用 乌兰巴托 马上。 ... 也 听起来很像 乌兰巴托

    然而,撇开技术问题不谈,我承认你可能在几乎所有的平台上都能找到你的“解决方案” 只要代码和示例中一样简单 . 不过,这并不能说明 好的 解决方案。事实上,我认为这甚至不是 可接受的 解决方案,因为输入法代码从来没有这么简单。多年来,它将得到扩展、改变、变异和扭曲,然后它将悄无声息地失败,需要36小时令人麻木的调试轮班才能找到问题。我不知道你的情况,但每当我发现像这样的代码负责36小时的调试乐趣,我想扼杀这个可怜的傻瓜谁这样对我。

    GotW #23 ,对这个想法进行了一个又一个的剖析,最后得出结论 充满陷阱 经常出错 ,而且 使派生类的作者生活在地狱里 ...

        3
  •  9
  •   Puppy    14 年前

    如果A有一个const成员,你怎么可能分配给它?你在试图完成一些根本不可能的事情。您的解决方案与原始方案相比没有新的行为,这不一定是UB,但您的解决方案肯定是UB。

    为了更清晰起见,请编辑:

    Const cast并不总是引入未定义的行为。不过,你肯定是这么做的。除此之外,在放入之前不调用所有析构函数(甚至没有调用正确的析构函数)是未定义的,除非您确定t是POD类。此外,还有一些未定义的行为与各种形式的遗传有关。

    不尝试分配给常量对象。

        4
  •  1
  •   UncleBens    14 年前

    如果您确实希望有一个不可变(但可分配)的成员,那么没有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';
    }
    
        5
  •  0
  •   Roddy    14 年前

    阅读此链接:

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

    特别地。。。

    据说这个技巧可以防止代码 重叠。但是,它有一些 严重的缺陷。为了工作,Cs 它已删除的指针,因为 随后的复制构造函数调用 当它将新值重新分配给char时 阵列。

        6
  •  0
  •   André Caron    14 年前

    const )成员们,这根本没有任何意义,不管是否有未定义的行为。

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

    阿飞,这里没有未定义的行为,因为 c 不是 static const 实例,或者无法调用复制分配运算符。然而, const_cast 康斯特 主要是为了解决 常量 -正确的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); 抛出异常,对象将被销毁两次。在这种情况下,这不会是一个问题,但如果你碰巧有重大的清理,你会后悔。

    编辑 :如果你班上有这个 常量 operator== 诸如此类),那么以下可能是有意义的:

    A& operator=(const A& assign) 
    { 
        // Copy all but `const` member `c`.
        // ...
    
        return *this;
    }