【问题标题】:const_cast a const member in a class constructorconst_cast 类构造函数中的 const 成员
【发布时间】:2013-04-11 23:13:10
【问题描述】:

当我希望类的成员变量在类的生命周期内保持不变时,我有时会使用 const_cast,但它需要在构造函数期间是可变的。示例:

struct qqq {
 const vector<foo> my_foo;

  qqq(vector<foo>* other) {
    vector<foo>& mutable_foo = const_cast<vector<foo>&>(my_foo)
    other->swap(mutable_foo);
  }
};

我曾假设在构造函数中执行此操作基本上没问题,因为此时没有其他人依赖它,因此它不会与优化等交互不良。

但是最近有人告诉我这是“未定义的行为”,并且在任何情况下构造 const 对象后对其进行变异基本上都是非法的。

有人可以澄清一下吗?这是不好的/未定义的行为/要做的事情吗?

【问题讨论】:

  • 您不应该将向量 other 作为指针传递给构造函数,除非您明确希望允许用户传递 nullptr,并适当地处理这种情况。
  • const vector 成员似乎很奇怪。这与让访问器函数返回对成员的 const 引用有什么不同?
  • Konrad:你可以假装 arg 是一个引用。不影响我的问题。
  • Praetorian:在qqq创建后,直到qqq被销毁,向量不应该被修改。 const 强制执行此操作。
  • @Praetorian 通过强制执行不变量来保护自己(以及后来修改代码的其他开发人员)是一件非常明智的事情。

标签: c++ constructor constants


【解决方案1】:

这是未定义的行为。根据 C++11 标准的第 7.1.6.1/4 段:

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

在这种情况下,您似乎希望您的对象在构造后“变为”常量。这不可能。

如果你的vectorconst,你应该在构造函数的initialization list 中初始化它:

qqq(vector<foo>& other) 
    : my_foo(std::move(other)) 
//  ^^^^^^^^^^^^^^^^^^^^^^^^^^^
{
}

注意,除非你有充分的理由通过指针传递——在这种情况下,你还应该检查指针是否非空——你应该考虑通过引用传递(如上所示),这是常见的练习。

更新:

正如 Pete Becker 在 cmets 中正确指出的那样,正确的设计表明从 vector 参数移动的决定应该属于qqq 的构造函数的调用者,而不是到构造函数本身。

如果构造函数总是应该从它的参数中移动,那么你可以让它接受一个右值引用,明确构造函数本身对调用者的期望:

qqq(vector<foo>&& other) 
//             ^^
    : my_foo(std::move(other)) 
//  ^^^^^^^^^^^^^^^^^^^^^^^^^^^
{
}

这样,调用者必须在qqq的构造函数的输入中提供一个右值

std::vector<foo> v;
// ...
qqq q1(v); // ERROR!
qqq q2(std::move(v)); // OK! Now the client is aware that v must be moved from

【讨论】:

  • 移动的决定应该由调用者做出,而不是被调用者。
  • @PeteBecker:对。我扩展了我的答案。
  • 但是不需要构造函数中的move,是吗?它已经是一个右值引用;如果目标类型(此处为vector&lt;foo&gt;)具有移动构造函数,则将使用该构造函数。
  • @PeteBecker: other 是一个右值引用,但它本身就是一个左值,因为它是一个命名对象。所以std::move还是有必要的
【解决方案2】:

是的,它确实是 UB(未定义行为)。 const 对象一旦初始化就无法修改。你应该做的是使用成员初始化器列表,也许与一个函数一起使用:

struct qqq {
  const vector<foo> my_foo;

  qqq(vector<foo> *other) : my_foo(initialiseFoo(*other)) {}

  static vector<foo> initialiseFoo(vector<foo> &other) {
    vector<foo> tmp;
    other.swap(tmp);
    return tmp;
  }
};

一个体面的优化器应该能够摆脱临时性。

如果能用C++11,其实更简单:

struct qqq {
  const vector<foo> my_foo;

  qqq(vector<foo> *other) : my_foo(std::move(*other))
  {
    other->clear();  //Just in case the implementation of moving vectors is really weird
  }
};

【讨论】:

  • 为什么不只是 std::move 初始化列表中的向量?
  • @Praetorian 当你发表评论时,我已经在输入 C++11 版本了。
  • @KonradRudolph 移动清除容器的标准保证吗?我知道复杂性几乎暗示了这一点,但从技术上讲,*other 不是处于“有效但未指定的状态”吗?
  • @Angew 这有什么关系?你为什么要关心other 处于什么状态?相关的是,在构造此对象后,调用者不应在当前状态下进一步使用 other
  • @Praetorian 我这样做是为了保留原始语义(至少是那些没有 UB 的语义),它使 *other 处于明确定义的状态 - 空(通过与默认构造的交换vector)。
猜你喜欢
  • 1970-01-01
  • 2011-10-22
  • 2017-02-10
  • 1970-01-01
  • 2021-09-04
  • 1970-01-01
  • 1970-01-01
  • 2011-12-07
  • 2011-09-13
相关资源
最近更新 更多