【问题标题】:When is it ok to modify a value when you remove const with const_cast?当您使用 const_cast 删除 const 时,何时可以修改值?
【发布时间】:2015-02-02 12:14:41
【问题描述】:

根据§7.1.​5.1/4:

除了可以修改任何声明为 mutable (7.1.1) 的类成员外,任何在 const 对象的生命周期 (3.8) 期间修改它的尝试都会导致未定义的行为。

所以我的问题变成了:一个对象什么时候是一个 const 对象?

特别是,非 const 对象中的 const 成员是否被视为 const 对象?

class Foo {
    const Bar bar;

    void replaceBar(Bar bar2) {
        *(const_cast<Bar *>&bar) = bar2;  // Undefined behavior?
    }
}

这是因为我有一个不可变的类(所有字段都是 const),但我想要一个移动构造函数,它在技术上修改传入的值。在这种情况下我可以接受“作弊”,因为它不会破坏逻辑常数。

【问题讨论】:

  • 没错,确实是UB。
  • 如果您要定义移动构造函数,那么您的成员之一可能是指针。在哪种情况下,使用指向 const 对象的可变指针而不是指向 const 对象的 const 指针是可以接受的?如果它不是指针或不可复制,那么移动构造函数不太可能有帮助。
  • @sjdowling 不一定。一个std::string 会很高兴离开。
  • 我不同意“技术上”的分类。它确实修改了传入的值,并且它确实破坏了您所谓的“逻辑常量”。为什么编译器不应该假设在Foo a; Foo b(move(a)); 中,a.bar 没有被修改,如果你将它定义为const?如果 a 的任何析构函数被内联,如果在编译时知道构造函数的效果,那么这些析构函数中的条件可以很容易地被优化掉。

标签: c++ const-cast


【解决方案1】:

让我们做一个完整的例子:

struct Bar { int x; };

struct Foo {
  const Bar bar;
  Foo( int x ):bar(x) {}

  void replaceBar(Bar bar2) {
    *(const_cast<Bar *>&bar) = bar2;  // Undefined behavior?
  }
};

现在,让我们打破世界。

int main() {
  Foo f(3);
  Bar b = {2};
  f.replaceBar(b);
  std::cout << f.bar.x << "\n";
}

上面可以并且可能应该输出3,因为const对象Bar是用x=3创建的。编译器可以而且应该假设 const 对象在其整个生命周期内都不会改变。

让我们更多地打破世界:

struct Bar {
  int* x;
  Bar(int * p):x(p) {}
  ~Bar(){ if (x) delete x; }
  Bar(Bar&& o):x(o.x){o.x=nullptr;}
  Bar& operator=(Bar&& o){
    if (x) delete x;
    x = o.x;
    o.x = nullptr;
  }
  Bar(Bar const&)=delete;
  Bar& operator=(Bar const&)=delete;
};

struct Foo {
  const Bar bar;
  Foo( int* x ):bar(x) {}

  void replaceBar(Bar bar2) {
    *(const_cast<Bar *>&bar) = bar2;  // Undefined behavior?
  }
};

现在同一个游戏可能会导致编译器删除某些东西两次。

int main() {
  int* p1 = new int(3);
  Foo f( p1 );
  Bar b( new int(2) );
  f.replaceBar(std::move(b));
}

并且编译器将在replaceBar 中删除一次p1,并且应该在main 的末尾也将其删除。它可以做到这一点,因为您保证 f.bar.x 将保持不变 (const) 直到其范围结束,然后您违反了 replaceBar 中的承诺。

现在,这只是编译器有理由做的事情:一旦你修改了一个声明为const 的对象,编译器就可以做任何事情,因为你调用了未定义的行为。鼻恶魔,时间旅行——什么都可以抢。

编译器利用某些行为未定义(也就是不允许)的事实来优化。

【讨论】:

    【解决方案2】:

    简单的规则是:如果原始对象不是 const,则可以抛弃 const。因此,如果您有一个非 cont 对象,并且例如将 const 引用传递给一个函数,那么在函数中丢弃 const 是合法的。 在您的示例中,原始对象是 const,因此放弃 const 是未定义的行为。

    【讨论】:

    • 更准确地说:允许强制转换 constness,但不允许滥用它来尝试修改对象。
    • @hvd ... 如果原始对象是 const。 (如果不是 const,抛弃 const 和修改是粗鲁的,但合法)(更准确地说!)(并添加更多的括号,例如:() (())() ((() ())()))。
    • @Yakk 非常正确。不过,这个答案和我所指的对象定义为const。 :)
    • Scott Meyers 有一个很好的例子,说明什么时候可以抛弃 constness,如 this SO 中所述。不过,我不建议在任何其他情况下这样做。
    猜你喜欢
    • 1970-01-01
    • 2011-03-29
    • 2012-07-14
    • 2012-01-04
    • 1970-01-01
    • 2019-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多