rvalue结构的成员是右值还是左值?

时间:2010-02-08 08:07:40

标签: c++ structure lvalue rvalue

返回结构的函数调用是一个右值表达式,但它的成员呢? 这段代码适用于我的g ++编译器,但是gcc给出了一个错误,说“左值作为赋值的左操作数”:

struct A
{
    int v;
};

struct A fun()
{
    struct A tmp;
    return tmp;
}

int main()
{
    fun().v = 1;
}

gcc将fun().v视为左值,我可以理解 但是g ++并不认为赋值表达式是错误的。这是否意味着fun1()。v是C ++中的左值? 现在的问题是,我搜索了C ++ 98/03标准,没有发现任何关于fun().v是左值还是左值的说法。
那么,它是什么?

6 个答案:

答案 0 :(得分:13)

右值表达式的成员是右值。

5.3.5 [expr.ref]中的标准状态:

  

如果声明E2具有类型   “引用T”,然后E1.E2是   左值[...]    - 如果E2是非静态数据成员,并且E1的类型是“cq1 vq1 X”,并且   E2的类型是“cq2 vq2 T”,   表达式指定命名成员   由第一个指定的对象   表达。如果E1是左值,那么   E1.E2是左值。

答案 1 :(得分:2)

编辑:好的,我想我终于有了标准的东西:

请注意,v的类型为int,其中包含内置赋值运算符:

  

13.3.1.2表达式中的运算符

     

4对于内置赋值运算符,左操作数的转换受到如下限制:    - 没有引入临时值来保存左操作数,[...]

fun1()应该返回引用。函数的非引用/指针返回类型是r值。

  

3.10 Lvalues and rvalues

     

5调用不返回左值引用的函数的结果是rvalue [...]

因此,fun1().v是一个右值。

  

8.3.2参考文献

     

2声明的引用类型   使用&被称为左值参考,   和声明的引用类型   使用&&被称为右值   参考。左值参考和   右值引用是不同的类型。

答案 2 :(得分:2)

现在是了解 xvalues glvalues 的好时机。

Rvalues 可以是两种类型 - prvalues xvalues 。根据新的C ++ 17标准

  

prvalue 是一个表达式,其评估初始化运算符的对象,位字段或操作数,由其出现的上下文指定。

所以你的例子中的$('.add-post-button').on('click', function (){ console.log($(this).closest('media').children('card-title').html()) }); 评估为prvalue(这是一个rvalue)。这也告诉我们fun()不是prvalue,因为它不是vanilla初始化。

Xvalues 也是rvalues,如此定义

  

xvalue (“eXpiring”值)也指对象,通常接近其生命周期的末尾(例如,可以移动其资源)。某些涉及右值引用(8.3.2)的表达式产生xvalues。 [示例:调用返回类型为对象类型的右值引用的函数的结果是xvalue(5.2.2)。 - 结束例子]

除了右值,另一个伞值类别是 glvalue ,它有两种类型 xvalues 和传统的左值

此时我们已经定义了基本价值类别。这可以像这样可视化

enter image description here

类别 glvalue 可以被广泛地认为意味着在移动语义成为事物之前左值应该是什么意思 - 可以在左侧的东西一种表达。 glvalue 表示广义左值。

如果我们查看 xvalue 的定义,那么如果它接近其生命周期的末尾,它会说 xvalue 。在您的示例中,fun().v接近其生命周期的末尾。所以它的资源可以移动。由于它的资源可以移动,因此它不是左值,因此您的表达式适用于唯一剩下的叶值类别 - xvalue

答案 3 :(得分:0)

我注意到gcc往往很少有关于在赋值表达式中使用rvalues作为左值的函数。例如,这可以很好地编译:

class A {
};

extern A f();

void g()
{
   A myA;
   f() = myA;
}

为什么这是合法的,这不是(即它不编译)虽然真的让我困惑:

extern int f();

void g()
{
   f() = 5;
}
恕我直言,标准委员会对左值,左值和可以使用的地方有一些解释。这是我对this question about rvalues如此感兴趣的原因之一。

答案 4 :(得分:0)

如果您认为编译器将为您生成默认构造函数,默认复制构造函数和默认复制赋值运算符,以防您的struct / class不包含引用成员,那么很明显。然后,想一想标准允许你在临时对象上调用成员方法,也就是说,你可以在非常量临时对象上调用非const成员。

见这个例子:

struct Foo {};
Foo foo () {
    return Foo();
}

struct Bar {
private:
    Bar& operator = (Bar const &); // forbid
};
Bar bar () {
    return Bar();
}
int main () {
    foo() = Foo(); // okay, called operator=() on non-const temporarie
    bar() = Bar(); // error, Bar::operator= is private
}

如果你写

struct Foo {};
const Foo foo () { // return a const value
    return Foo();
}

int main () {
    foo() = Foo(); // error
}

即。如果让函数foo()返回 const 临时,则会发生编译错误。

为了使示例完整,以下是如何调用const temporarie的成员:

struct Foo {
    int bar () const { return 0xFEED; }
    int frob ()      { return 0xFEED; }
};
const Foo foo () {
    return Foo();
}

int main () {
    foo().bar(); // okay, called const member method
    foo().frob(); // error, called non-const member of const temporary
}

您可以将临时的生命周期定义为当前表达式。然后这就是为什么你也可以修改成员变量;如果你做不到,那么能够调用非常规成员方法的可能性就会导致荒谬。

编辑:以下是所需的引用:

12.2临时对象:

  • 3)[...]临时对象作为评估全表达式(1.9)的最后一步被销毁,该表达式(词法上)包含创建它们的点。 [...]

然后(或更好,之前)

3.10 Lvalues和rvalues:

  • 10)为了修改对象,对象的左值是必要的,除了在某些情况下也可以使用类类型的右值来修改它的指示对象。 [示例:调用对象(9.3)的成员函数可以修改对象。 ]

使用示例:http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Named_Parameter

答案 5 :(得分:-2)

你的代码没有场景。返回的结构在堆栈上分配,因此分配结果将立即丢失。

您的函数应该通过以下方式分配A的新实例:

new A()

在这种情况下更好的签名

A* f(){ ...

或返回现有实例,例如:

static A globalInstance;
A& f(){ 
  return globalInstance;
}