避免未定义的行为:将临时变量传递给具有 const ref 成员变量的“std::function”
Avoiding undefined behaviour: passing a temporary to a `std::function` which has a const ref member variable
以下示例是生产代码中的简化版本
#include <string>
#include <functional>
#include <iostream>
#include <vector>
struct A
{
std::string myString = "World";
};
struct B
{
void operator()()
{
std::cout << a.myString;
}
const A& a;
};
std::vector<std::function<void()>> v;
void Store(std::function<void()> myFunc)
{
v.emplace_back(myFunc);
}
void Print()
{
v[0]();
}
int main()
{
A a; a.myString = "Hello";
Store(B{a}); // temporary passed with a const-ref of on-the-stack `A`
Print();
}
在生产代码中,访问字符串 A
(即通过 vector
调用函数,进而访问 A
中的 myString
)会导致崩溃。编译器资源管理器似乎没有问题,但是如果未定义此行为,则输出可能不可信:https://godbolt.org/z/cPPKeK9zd
假设这是未定义的行为,我只能将 const &
存储到 A
,我可以在 Store(...)
函数中做什么来发出编译器错误并尝试捕获编译时的未定义行为。
这个例子没有未定义的行为。
调用 Store
从类型 B
的临时对象复制初始化类型 std::function<void()>
的参数。在这样做时,std::function
使用完美转发来初始化它自己的 B
类型的内部对象,因此它是从原始临时对象移动构造的。
对 emplace_back
的调用复制构造函数,因此复制构造类型 B
的内部对象。
初始 B
对象是一个临时对象是无关紧要的,因为向量中函数内的副本不是。此 B
副本中对 A
的引用仍指向同一个 A
对象,该对象仍在其生命周期内。
以下示例是生产代码中的简化版本
#include <string>
#include <functional>
#include <iostream>
#include <vector>
struct A
{
std::string myString = "World";
};
struct B
{
void operator()()
{
std::cout << a.myString;
}
const A& a;
};
std::vector<std::function<void()>> v;
void Store(std::function<void()> myFunc)
{
v.emplace_back(myFunc);
}
void Print()
{
v[0]();
}
int main()
{
A a; a.myString = "Hello";
Store(B{a}); // temporary passed with a const-ref of on-the-stack `A`
Print();
}
在生产代码中,访问字符串 A
(即通过 vector
调用函数,进而访问 A
中的 myString
)会导致崩溃。编译器资源管理器似乎没有问题,但是如果未定义此行为,则输出可能不可信:https://godbolt.org/z/cPPKeK9zd
假设这是未定义的行为,我只能将 const &
存储到 A
,我可以在 Store(...)
函数中做什么来发出编译器错误并尝试捕获编译时的未定义行为。
这个例子没有未定义的行为。
调用 Store
从类型 B
的临时对象复制初始化类型 std::function<void()>
的参数。在这样做时,std::function
使用完美转发来初始化它自己的 B
类型的内部对象,因此它是从原始临时对象移动构造的。
对 emplace_back
的调用复制构造函数,因此复制构造类型 B
的内部对象。
初始 B
对象是一个临时对象是无关紧要的,因为向量中函数内的副本不是。此 B
副本中对 A
的引用仍指向同一个 A
对象,该对象仍在其生命周期内。