我正在尝试理解C++ 11的概念.
我所说的标准草案:
xvalue("eXpiring"值)也指对象,通常接近其生命周期的末尾(例如,可以移动其资源).xvalue是涉及rvalue引用的某些表达式的结果(8.3.2).[示例:调用返回类型为右值引用的函数的结果是xvalue. - 末端的例子]
那么,产生xvalues的"某种表达式" 到底是什么?规范的这一部分没有详细说明这些表达式的列表.
我理解左值和右值(至少我认为,我理解).
(答案提示.)
鉴于N3290,§7.1.6.2p4,其中列表项目未编号,但为了方便我们编号:
decltype(e)表示的类型定义如下:
- 如果e是未表示的id-expression或未加密的类成员访问(5.2.5),则decltype(e)是e所命名的实体的类型.如果没有这样的实体,或者如果e命名了一组重载函数,那么该程序就会形成错误;
- 否则,如果e是x值,则decltype(e)是T &&,其中T是e的类型;
- 否则,如果e是左值,则decltype(e)是T&,其中T是e的类型;
- 否则,decltype(e)是e的类型.
decltype(0 + 0)指定的类型是什么?
第1项不适用,2可能,但如果没有,则3不适用,4将是结果.那么,什么是xvalue,是0 + 0和xvalue?
§3.10p1:
xvalue("eXpiring"值)也指对象,通常接近其生命周期的末尾(例如,可以移动其资源).xvalue是涉及rvalue引用的某些表达式的结果(8.3.2).
我在§8.3.2中没有看到任何有用的内容,但我知道"0 + 0"不涉及任何右值引用.文字0是一个prvalue,它是"一个不是xvalue的rvalue"(§3.10p1).我相信"0 + 0"也是一个prvalue.如果这是真的,"decltype(0 + 0)"将是int(不是int &&).
我的解释错过了什么吗?这段代码是否格式良好?
decltype(0 + 0) x; // Not initialized.
Run Code Online (Sandbox Code Playgroud)
该代码编译在GCC 4.7.0 20110427和Clang 2.9(主干126116)上.例如,如果decltype指定了int &&类型,那么它将不会格式良好.
我试图理解C++ 11中的左值和右值.所以我写了一个测试代码:
int x = 10;
int foo() { return x; }
int& bar() { return x; }
int&& baz() { return 10; }
int main() {
int& lr1 = 10; // error: lvalue references rvalue
int& lr2 = x; // ok
int& lr3 = foo(); // error: lvalue references rvalue
int& lr4 = bar(); // ok
int& lr5 = baz(); // error: lvalue references rvalue
int&& rr1 = 10; // ok
int&& rr2 = x; // error: rvalue references …Run Code Online (Sandbox Code Playgroud) 有点令人惊讶(对我来说),以下两个程序编译成不同的输出,后者有更好的性能(用gcc和clang测试):
#include <vector>
int main()
{
std::vector<int> a(2<<20);
for(std::size_t i = 0; i != 1000; ++i) {
std::vector<int> b(2<<20);
a = b;
}
}
Run Code Online (Sandbox Code Playgroud)
与
#include <vector>
int main()
{
std::vector<int> a(2<<20);
for(std::size_t i = 0; i != 1000; ++i) {
std::vector<int> b(2<<20);
a = std::move(b);
}
}
Run Code Online (Sandbox Code Playgroud)
有人可以向我解释为什么编译器会(或可以)不自动考虑b最后一个赋值中的xvalue并在没有显式强制转换的情况下应用移动语义std::move吗?
编辑:编译(g++|clang++) -std=c++11 -O3 -o test test.cpp
以下函数(按照我的意图)是获取右值并将其呈现为左值。
auto constexpr RtoL = [](auto&& r) -> decltype(auto) {
static_assert(std::is_rvalue_reference_v<decltype(r)>, "Gimme rvalues, not lvalues.");
return (r);
};
Run Code Online (Sandbox Code Playgroud)
我考虑在我可以保证 xvalue 没有真正被移动的情况下使用它(例如,它通过 转换为右值std::move,但没有利用它),所以我会用 xvalues 来调用它,而不是纯右值。
不管怎样,编译器(好吧,GCC 的版本)似乎对上面代码的有效性有不同的看法。具体来说,考虑到这种用法:
auto constexpr RtoL = [](auto&& r) -> decltype(auto) {
static_assert(std::is_rvalue_reference_v<decltype(r)>, "Gimme rvalues, not lvalues.");
return (r);
};
Run Code Online (Sandbox Code Playgroud)
GCC 11.2 认为它无效:
int main() {
int x{3};
RtoL(std::move(x));
}
Run Code Online (Sandbox Code Playgroud)
而 GCC 10.3 和其他编译器认为它是有效的。
此外,将 return 语句从
return (r);
Run Code Online (Sandbox Code Playgroud)
到
return static_cast<decltype(r)&>(r);
Run Code Online (Sandbox Code Playgroud)
让他们都同意代码是正确的。
从标准的角度来看,真相在哪里?
考虑下面的最小例子:
#include<utility>
struct S { };
int main() {
S s;
std::move(s) = S{};
}
Run Code Online (Sandbox Code Playgroud)
它编译没有错误.
如果我改用非类型,我会收到错误.
例如,下面的代码无法编译:
#include<utility>
int main() {
int i;
std::move(i) = 42;
}
Run Code Online (Sandbox Code Playgroud)
枚举,作用域枚举等也会发生同样的情况.
错误(来自GCC)是:
使用xvalue(rvalue reference)作为左值
这背后的理由是什么?
我想这是正确的,但我想了解我能用所有类型而不是非类型的方式做到这一点的原因.
这是一些示例代码:
#include <iostream>
class Foo
{
public:
explicit Foo(int x) : data(x) {};
Foo& operator++()
{
data += 1;
return *this;
}
void *get_addr()
{
return (void*)this;
}
friend Foo operator + (const Foo& lhs, const Foo& rhs);
friend std::ostream& operator << (std::ostream& os, const Foo& f);
private:
int data;
};
std::ostream& operator << (std::ostream& os, const Foo& f)
{
return (os << f.data);
}
Foo operator + (const Foo& lhs, const Foo& rhs)
{
return Foo(lhs.data + rhs.data);
} …Run Code Online (Sandbox Code Playgroud) 如果我写下面的代码:
#include <iostream>
using namespace std;
int main()
{
cout << &(int &&)123 << endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
然后g++抱怨:
foo.cc: In function ‘int main()’:
foo.cc:7:20: error: taking address of xvalue (rvalue reference)
Run Code Online (Sandbox Code Playgroud)
好的,多亏了什么是右值,左值,x值,glvalues和prvalues?我得到一个xvalue意味着它即将"过期",这是有道理的.但是现在如果我这样做:
#include <iostream>
using namespace std;
int main()
{
const int &x = (int &&)123;
cout << &x << endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这"工作"很好,将打印一个地址.所以,我有几个问题:
通常有一种方法可以知道右值参考的生命周期吗?
我目前正在撰写我的学位论文,它还涉及C++ 11背后的理论的一些解释,这很好,因为C++是我选择的编程语言,而且标准或多或少是免费提供的(N3337)让你自己迷失在.
然而,我试图准确而详细地解释新的xvalue类别时遇到了障碍.我的理解是临时对象总是一个xvalue但我在标准中找不到任何对此的引用.我的理解是函数的表达式的值类别调用具有非引用返回类型的函数是xvalue.标准说"xvalue是涉及rvalue-references的某种表达式的结果",这让我烦恼.例如:
TestClass { ... };
testClass createObject() { return testClass(); }
void someFunction(TestClass& testClass) { ... }
void someFunction(TestClass&& testClass) { ... }
someFunction(createObject());
Run Code Online (Sandbox Code Playgroud)
正如所料,上面将调用带有rvalue-reference作为参数的重载函数.然而,createObject()不返回rvalue-reference,它返回一个TestClass类型的临时对象.我现在的问题是,我必须解释其背后的原因.表达式"createObject()"评估为什么?如果它确实是一个xvalue,因为它返回一个临时对象,它背后的推理是明确的,并且在重载解析期间有利于rvalue-reference.如果没有,那么关于标准的这种行为的解释是什么?是否有某些我尚未找到的隐式转换逻辑?
如果有人能帮我解决这个问题,我真的很感激,因为即使经过几天的挖掘和阅读,我还没有想出一个合理的解释.非常感谢提前.
我对这个问题的广泛性感到抱歉,只是所有这些细节都是紧密相连的。
我一直在尝试理解两个特定值类别 - xvalues 和 prvalues 之间的区别,但我仍然感到困惑。
不管怎样,我试图为自己开发的“同一性”概念的心智模型是,应该保证具有同一性的表达式驻留在实际程序的数据存储器中。
由于这个原因,字符串文字是左值,它们保证在整个程序运行期间驻留在内存中,而数字文字是纯右值,并且可以假设存储在直接汇编中。
这似乎同样适用于std::move纯纯右值文字,即,在调用时,fun(1)我们只会在被调用者框架中获得参数左值,但在调用时,fun(std::move(1))左值的 xvalue“种类”必须保留在调用者框架中。
然而,这种心理模型至少不适用于临时对象,据我所知,临时对象应该始终在实际内存中创建(例如,如果像使用纯右值参数那样调用fun(MyClass())右值引用函数)。所以我认为这种思维模式是错误的。
那么,思考 xvalue 的“同一性”属性的正确方法是什么?我已经读过,通过身份我可以比较地址,但是如果我可以比较 2 的地址MyClass().member(根据 cppreference 的 xvalue),比方说通过右值引用将它们传递到某个比较函数中,那么我不明白为什么我可以不对 2 MyClass()s(纯右值)做同样的事情吗?
与此相关的另一个来源是此处的答案: 什么是移动语义?
请注意,尽管 std::move(a) 是右值,但其求值不会创建临时对象。这个难题迫使委员会引入第三个价值类别。可以绑定到右值引用的东西,即使它不是传统意义上的右值,也称为xvalue(到期值)。
但这似乎与“可以比较地址”无关,并且a)我不明白这与右值的“传统意义上”有什么不同;b) 我不明白为什么这样的原因需要语言中的新值类别(好吧,这允许为 OO 意义上的对象提供动态类型,但 xvalue 不仅仅指对象)。