【问题标题】:Is it valid C++ to cast an rvalue to a const pointer?将右值转换为 const 指针是否有效 C++?
【发布时间】:2014-05-03 01:07:50
【问题描述】:

匆忙中,需要一个指向对象的指针来传递给函数。我获取了一个未命名的临时对象的地址,令我惊讶的是它编译了(原始代码的警告被进一步降低,并且缺乏下面示例中存在的 const 正确性)。好奇的是,我在 Visual Studio 2013 中设置了一个带有警告的受控环境,并将警告视为错误。

考虑以下代码:

class Contrived {
  int something;
};

int main() {
  const Contrived &r  = Contrived();                    // this is well defined even in C++03, the object lives until r goes out of scope
  const Contrived *p1 = &r;                             // compiles fine, given the type of r this should be fine. But is it considering r was initialized with an rvalue?
  const Contrived *p2 = &(const Contrived&)Contrived(); // this is handy when calling functions, is it valid? It also compiles
  const int       *p3 = &(const int&)27;                // it works with PODs too, is it valid C++?

  return 0;
}

三个指针初始化或多或少都是一回事。问题是,这些初始化在 C++03、C++11 或两者下是有效的 C++ 吗?考虑到围绕右值引用进行了大量工作,我会单独询问 C++11 以防发生变化。像上面的例子那样分配这些值似乎不值得,但值得注意的是,如果将这些值传递给采用常量指针的函数并且您周围没有合适的对象或感觉不到,这可以节省一些输入就像在上面的一行上制作一个临时对象。

编辑:
根据答案,以上是有效的 C++03 和 C++11。我想就结果对象的生命周期提出一些额外的说明。

考虑以下代码:

class Contrived {
  int something;
} globalClass;
int globalPOD = 0;

template <typename T>
void SetGlobal(const T *p, T &global) {
  global = *p;
}

int main() {
  const int *p1 = &(const int&)27;
  SetGlobal<int>(p1, globalPOD);              // does *p still exist at the point of this call?

  SetGlobal<int>(&(const int&)27, globalPOD); // since the rvalue expression is cast to a reference at the call site does *p exist within SetGlobal

  // or similarly with a class
  const Contrived *p2 = &(const Contrived&)Contrived();
  SetGlobal<Contrived>(p2, globalClass);
  SetGlobal<Contrived>(&(const Contrived&)Contrived(), globalClass);

  return 0;
}

问题是对 SetGlobal 的调用中的一个或两个是否有效,因为它们传递了一个指向在 C++03 或 C++11 标准下调用期间将存在的对象的指针?

【问题讨论】:

    标签: c++ c++11 c++03


    【解决方案1】:

    rvalue 是一种表达式,而不是一种对象。我们谈论的是由Contrived() 创建的临时对象,说“这个对象是一个右值”是没有意义的。创建对象的表达式是一个右值表达式,但这是不同的。

    即使所讨论的对象是临时对象,它的生命周期也已延长。使用表示它的标识符r 对对象执行操作非常好。表达式r 是一个左值。

    p1 没问题。在p2p3 行上,引用的生命周期在该完整表达式的末尾结束,因此临时对象的生命周期也在该点结束。因此,在后续行中使用 p2p3 将是未定义的行为。初始化表达式可以用作函数调用的参数,如果这就是你的意思的话。

    【讨论】:

    • 感谢指正。是的,右值指的是表达式而不是对象。这是一个微妙的区别,但我想更精确/正确。我没有考虑参考结尾的范围(因为它只是一个演员表)。或者我认为对象的生命周期可能会再次被 const 指针延长(因为引用只是引擎盖下的指针,并且引用被定义为延长对象的生命周期)。好像不是这样?
    • 所以,还有一个问题或澄清点。您说“初始化表达式可以用作函数调用的参数。”那么,我可以做这样的事情吗? void fn(const int *p){global = *p;}fn(&amp;(const int&amp;)27); 以及指向的“对象”p 将保证存在于函数中?
    • 在语言定义中,引用不是“引擎盖下的指针”。它是变量的别名。事实证明,如果通过引用将参数传递给函数,那么使用变量的地址通常是最简单的方法;但这不会改变语言定义,它专门讨论 const 引用延长生命周期,而不是指向 const 的指针。
    • An rvalue is a type of expression 不,右值是一个“值类别”。它根本不是“类型”。
    • 我已经用您的答案已经解决的对象生命周期的这些附加点更新了我的问题,并更正了右值术语。使用的术语适合值类别或右值的表达式定义,这些定义仍在此处的 cmets 中进行讨论。
    【解决方案2】:

    第一个很好:表达式r 实际上不是右值。

    另外两个在技术上也是有效的,但请注意,指针在完整表达式的末尾(分号处)会悬空,任何使用它们的尝试都会表现出未定义的行为。

    【讨论】:

      【解决方案3】:

      虽然通过const&amp; 传递右值是完全合法的,但您必须注意,您的代码以p2p3 中的无效指针结束,因为它们指向的对象的生命周期已经结束.

      为了举例说明这一点,请考虑以下经常用于通过引用传递临时值的代码:

      template<typename T>
      void pass_by_ref(T const&);
      

      这样的函数可以用左值或右值作为其参数调用(通常是这样)。在该函数中,您显然可以引用您的参数 - 毕竟它只是对 const 对象的引用......您基本上在没有函数帮助的情况下做同样的事情。

      事实上,在 C++11 中,你可以更进一步,获得一个指向临时的非常量指针:

      template<typename T>
      typename std::remove_reference<T>::type* example(T&& t)
      {
          return &t;
      }
      

      请注意,只有在使用左值调用此函数时,返回值指向的对象才会仍然存在(因为它的参数将变成typename remove_reference&lt;T&gt;::type&amp; &amp;&amp;,即typename remove_reference&lt;T&gt;::type&amp;)。

      【讨论】:

      • 如果函数是用一个临时右值调用的,它不是有效的 C++,因为它不允许使用右值的地址。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-10-20
      • 2017-01-19
      • 1970-01-01
      • 2017-05-15
      • 2019-08-21
      • 1970-01-01
      • 2010-12-14
      相关资源
      最近更新 更多