来自 std::pair 右值构造函数的垃圾值
Garbage value from std::pair rvalues constructor
编辑 在底部
我发现 pair
的构造函数有一个我不完全理解的行为。
所以我尝试用右值初始化一对,代码如下:
pair<vector<int> &&, int> a([]() -> vector<int> {
vector<int> b{1};
cout << &b << ' ' << b[0] << '\n';
return b;
}(), 0);
cout << &a.first << ' ' << a.first[0] << '\n';
输出为
0x62fdf0 1
0x62fdf0 14162480
显然 a.first
是垃圾。
然后我在网上找到pair
的构造函数是这样的:
pair (const first_type& a, const second_type& b);
template<class U, class V> pair (U&& a, V&& b);
所以我猜第二个正在使用?然后我尝试删除 &&
:
pair<vector<int>, int> a([]() -> vector<int> {
vector<int> b{1};
cout << &b << ' ' << b[0] << '\n';
return b;
}(), 0);
cout << &a.first << ' ' << a.first[0] << '\n';
但现在 a.first
的地址与 b
不同:
0x62fdf0 1
0x62fdc0 1
但是如果我删除外部无用的 pair
,代码将工作(即相同的地址和相同的值)。为什么?我怎样才能使 pair
起作用?
编辑
在尝试理解@cigien 的评论后,我将旧代码大幅缩减为
pair<int &&, int> a(1, 2);
cout << a.first << '\n';
提示我错误 warning: '<anonymous>' is used uninitialized in this function [-Wuninitialized]
。然后我读了 .
我试图通过阅读 pair
构造函数 here 的定义来消除警告。这导致以下(更香草)代码:
struct s_t {
int && first = 0;
} var;
int main() {
var.first = 2;
cout << var.first << '\n';
cout << var.first << '\n';
}
这段代码警告消失了。但是这段代码的输出是:
2
0
老实说,这给我留下了零线索。感谢任何帮助。
您正在创建 临时对象,对它们进行(右值)引用,然后在底层对象消失后尝试使用这些引用。
在第一个例子中,[]() -> vector<int> { ... }()
是类型为 vector<int>
的纯右值(创建对象的指令)表达式。 pair
构造函数需要对真实对象的引用,而不是创建对象的指令,因此 在 调用构造函数之前,调用 lambda 并临时 vector<int>
被建造。然后构造函数将对该对象的引用存储到 pair
中,然后在构造函数完成并且控制权返回给您后销毁该对象。因此,该对的第一个元素现在是垃圾。
在第二种情况下,构造函数获取对临时对象 vector<int>
的引用(并且以相同的方式创建临时对象),但是这次,而不是存储引用(即构造一个 vector<int>&&
来自 vector<int>&&
),它从引用构造了一个 vector<int>
。从 vector<int>&&
构造 vector<int>
是由 vector<int>
的构造函数的特定重载处理的(此重载具有特殊名称“移动构造函数”)。此构造函数获取临时对象内堆分配的句柄,并简单地将所有权授予该对内的对象。现在,一旦临时文件被销毁,实际数据仍然存在,但句柄(即 vector<int>
对象)本身位于不同的位置。
你的最后一个例子和第一个例子有同样的问题。初始化 var
时,0
(int
纯右值)不引用真实对象,因此您不能仅将引用 var.first
绑定到它。 0
物化为一个临时对象,var.first
指向那个对象,然后临时对象被销毁。 var.first
现在是悬而未决的,你不能用它做任何事情,所以代码的其余部分是 UB,找出“幕后”出了什么问题没有多大意义(尽管它看起来确实很有趣)。事实上,如此无用 从这样的临时对象初始化引用成员 var
的初始化是非法的(应该是一个“硬”编译错误)缺陷报告 1696。(但旧的编译器可能会接受它,甚至当前的编译器可能(错误地)将其降级为警告。)
现在,如果您真的真的想在 pair
中“直接”构造 vector<int>
而无需调用其移动构造函数,您可以这样做,创建一个暂停构造 vector<int>
直到构造 pair
的字段。
template<typename F>
struct initializing {
F self;
initializing(F self) : self(std::move(self)) { }
operator decltype(auto)() { return self(); }
};
现在说
pair<vector<int>, int> a(
initializing([]() -> vector<int> {
vector<int> b{1};
cout << &b << ' ' << b[0] << '\n';
return b;
}), 0);
现在,lambda 是匿名闭包类型的纯右值。它具体化为一个临时对象,并且通过将对该临时对象的引用传递给 initializing
的构造函数来生成 initializing<that_anonymous_type>
纯右值。现在,该纯右值也被具体化为一个临时值,它调用构造函数并通过调用闭包类型的移动构造函数(在本例中为 no-op)构造 initializing
的 self
字段对临时闭包类型对象的引用。然后对该临时 initializing
对象的引用被传递给 pair
的构造函数,该构造函数从 initializing
构造一个 vector<int>
。这将调用 initializing
中定义的转换函数,并将结果对象设置为该对的未初始化字段。然后,转换函数调用 lambda,结果对象也设置为该对的字段。然后 lambda 通过在 return
处移动 b
直接初始化该结果对象(对的第一个元素)(或者,当应用 NRVO 时,b
成为结果的名称 object/first 对的元素,并在 lambda 的顶部初始化)。这样,除了在 lambda 中写入的一两个“可见”之外,没有 vector
构造函数被调用,如果应用 NRVO,b
和 a.first
的地址将是相同的。稍微危险一点的版本是
template<typename F>
struct initializing {
F &&self;
initializing(F &&self) : self(std::forward<F>(self)) { }
operator decltype(auto)() { return std::forward<F>(self)(); }
};
这也消除了从 lambda 中具体化的对象的移动。
编辑 在底部
我发现 pair
的构造函数有一个我不完全理解的行为。
所以我尝试用右值初始化一对,代码如下:
pair<vector<int> &&, int> a([]() -> vector<int> {
vector<int> b{1};
cout << &b << ' ' << b[0] << '\n';
return b;
}(), 0);
cout << &a.first << ' ' << a.first[0] << '\n';
输出为
0x62fdf0 1
0x62fdf0 14162480
显然 a.first
是垃圾。
然后我在网上找到pair
的构造函数是这样的:
pair (const first_type& a, const second_type& b);
template<class U, class V> pair (U&& a, V&& b);
所以我猜第二个正在使用?然后我尝试删除 &&
:
pair<vector<int>, int> a([]() -> vector<int> {
vector<int> b{1};
cout << &b << ' ' << b[0] << '\n';
return b;
}(), 0);
cout << &a.first << ' ' << a.first[0] << '\n';
但现在 a.first
的地址与 b
不同:
0x62fdf0 1
0x62fdc0 1
但是如果我删除外部无用的 pair
,代码将工作(即相同的地址和相同的值)。为什么?我怎样才能使 pair
起作用?
编辑
在尝试理解@cigien 的评论后,我将旧代码大幅缩减为
pair<int &&, int> a(1, 2);
cout << a.first << '\n';
提示我错误 warning: '<anonymous>' is used uninitialized in this function [-Wuninitialized]
。然后我读了
我试图通过阅读 pair
构造函数 here 的定义来消除警告。这导致以下(更香草)代码:
struct s_t {
int && first = 0;
} var;
int main() {
var.first = 2;
cout << var.first << '\n';
cout << var.first << '\n';
}
这段代码警告消失了。但是这段代码的输出是:
2
0
老实说,这给我留下了零线索。感谢任何帮助。
您正在创建 临时对象,对它们进行(右值)引用,然后在底层对象消失后尝试使用这些引用。
在第一个例子中,[]() -> vector<int> { ... }()
是类型为 vector<int>
的纯右值(创建对象的指令)表达式。 pair
构造函数需要对真实对象的引用,而不是创建对象的指令,因此 在 调用构造函数之前,调用 lambda 并临时 vector<int>
被建造。然后构造函数将对该对象的引用存储到 pair
中,然后在构造函数完成并且控制权返回给您后销毁该对象。因此,该对的第一个元素现在是垃圾。
在第二种情况下,构造函数获取对临时对象 vector<int>
的引用(并且以相同的方式创建临时对象),但是这次,而不是存储引用(即构造一个 vector<int>&&
来自 vector<int>&&
),它从引用构造了一个 vector<int>
。从 vector<int>&&
构造 vector<int>
是由 vector<int>
的构造函数的特定重载处理的(此重载具有特殊名称“移动构造函数”)。此构造函数获取临时对象内堆分配的句柄,并简单地将所有权授予该对内的对象。现在,一旦临时文件被销毁,实际数据仍然存在,但句柄(即 vector<int>
对象)本身位于不同的位置。
你的最后一个例子和第一个例子有同样的问题。初始化 var
时,0
(int
纯右值)不引用真实对象,因此您不能仅将引用 var.first
绑定到它。 0
物化为一个临时对象,var.first
指向那个对象,然后临时对象被销毁。 var.first
现在是悬而未决的,你不能用它做任何事情,所以代码的其余部分是 UB,找出“幕后”出了什么问题没有多大意义(尽管它看起来确实很有趣)。事实上,如此无用 从这样的临时对象初始化引用成员 var
的初始化是非法的(应该是一个“硬”编译错误)缺陷报告 1696。(但旧的编译器可能会接受它,甚至当前的编译器可能(错误地)将其降级为警告。)
现在,如果您真的真的想在 pair
中“直接”构造 vector<int>
而无需调用其移动构造函数,您可以这样做,创建一个暂停构造 vector<int>
直到构造 pair
的字段。
template<typename F>
struct initializing {
F self;
initializing(F self) : self(std::move(self)) { }
operator decltype(auto)() { return self(); }
};
现在说
pair<vector<int>, int> a(
initializing([]() -> vector<int> {
vector<int> b{1};
cout << &b << ' ' << b[0] << '\n';
return b;
}), 0);
现在,lambda 是匿名闭包类型的纯右值。它具体化为一个临时对象,并且通过将对该临时对象的引用传递给 initializing
的构造函数来生成 initializing<that_anonymous_type>
纯右值。现在,该纯右值也被具体化为一个临时值,它调用构造函数并通过调用闭包类型的移动构造函数(在本例中为 no-op)构造 initializing
的 self
字段对临时闭包类型对象的引用。然后对该临时 initializing
对象的引用被传递给 pair
的构造函数,该构造函数从 initializing
构造一个 vector<int>
。这将调用 initializing
中定义的转换函数,并将结果对象设置为该对的未初始化字段。然后,转换函数调用 lambda,结果对象也设置为该对的字段。然后 lambda 通过在 return
处移动 b
直接初始化该结果对象(对的第一个元素)(或者,当应用 NRVO 时,b
成为结果的名称 object/first 对的元素,并在 lambda 的顶部初始化)。这样,除了在 lambda 中写入的一两个“可见”之外,没有 vector
构造函数被调用,如果应用 NRVO,b
和 a.first
的地址将是相同的。稍微危险一点的版本是
template<typename F>
struct initializing {
F &&self;
initializing(F &&self) : self(std::forward<F>(self)) { }
operator decltype(auto)() { return std::forward<F>(self)(); }
};
这也消除了从 lambda 中具体化的对象的移动。