对不起,标题很差,但我所看到的很奇怪,很难简洁地解释。
基本上,我们optional<map<string, string>>的代码中有一个通过 getter/setter 访问的,有时当我们检查值时,我们会得到非常奇怪的结果。这是重现问题的简化代码:
#include <optional>
#include <map>
#include <iostream>
using namespace std;
optional<map<string, string>> optmap;
static void Set(optional<map<string, string>> m);
static optional<map<string, string>> Get();
static void PrintMap(map<string, string> m);
int main(int const argc, char const * const *argv)
{
map<string, string> sample;
sample.emplace("testtesttesttest1", "testtesttesttest1");
sample.emplace("testtesttesttest2", "testtesttesttest2");
sample.emplace("testtesttesttest3", "testtesttesttest3");
cout << "sample:" << endl;
PrintMap(sample);
Set(sample);
map<string, string> result = Get().value();
cout << "result:" << endl;
PrintMap(result);
cout << "function call:" << endl;
PrintMap(Get().value());
cout << "inline iteration:" << endl;
for (auto &item : Get().value())
{
cout << item.first << ", " << item.second << endl;
}
}
static void Set(optional<map<string, string>> m)
{
optmap = m;
}
static optional<map<string, string>> Get()
{
return optmap;
}
static void PrintMap(map<string, string> m)
{
for (auto &item : m)
{
cout << item.first << ", " << item.second << endl;
}
}
Run Code Online (Sandbox Code Playgroud)
我编译使用g++ -std=c++17并在我最近的运行中得到了这个输出:
$ ./a.out
sample:
testtesttesttest1, testtesttesttest1
testtesttesttest2, testtesttesttest2
testtesttesttest3, testtesttesttest3
result:
testtesttesttest1, testtesttesttest1
testtesttesttest2, testtesttesttest2
testtesttesttest3, testtesttesttest3
function call:
testtesttesttest1, testtesttesttest1
testtesttesttest2, testtesttesttest2
testtesttesttest3, testtesttesttest3
inline iteration:
@?M?OVtesttest1, ??M?OVtesttest1
??M?OVtesttest2, ??M?OVtesttest2
??M?OVtesttest3, testtest3
Run Code Online (Sandbox Code Playgroud)
请注意,在我们使用for (auto &item : Get().value()). 更奇怪的是,这似乎只发生在一定长度的字符串上。如果值的长度小于 16 个字符,则没有问题。如果我将地图更改为包含以下内容:
sample.emplace("fifteencharokay", "15");
sample.emplace("sixteencharweird", "16");
Run Code Online (Sandbox Code Playgroud)
我得到这个输出:
$ ./a.out
sample:
fifteencharokay, 15
sixteencharweird, 16
result:
fifteencharokay, 15
sixteencharweird, 16
function call:
fifteencharokay, 15
sixteencharweird, 16
inline iteration:
fifteencharokay, 15
harweird, 16
Run Code Online (Sandbox Code Playgroud)
(注意"sixteencharweird"已"harweird"在最后一行被截断)
这里发生了什么?为什么我们在这种非常特殊的情况下会遇到问题(长字符串并直接迭代函数调用的结果)?我通过这种方式迭代是否违反了某种 C++ 规则?
在这个循环中:
for (auto &item : Get().value())
Run Code Online (Sandbox Code Playgroud)
您正在调用未定义的行为,因为返回的临时值Get()将在完整表达式结束时消失,并且.value()您的范围 for 循环将迭代的引用不再存在的内存。
您注意到长度小于 16 个字符的字符串的奇怪行为可能是由于小字符串优化造成的。由于字符串保留在短字符串的内部缓冲区中,您仍然可以看到那里的内存。当然,这仍然是UB,你不能依赖它。
您可以通过执行以下操作来解决此问题:
auto const &g = Get();
for (auto &item : g.value())
Run Code Online (Sandbox Code Playgroud)
这是一个演示。
事实上,c++20 正是为了这个目的添加了 range-for with initializer 构造:
for (auto const &g = Get(); auto &item : g.value())
Run Code Online (Sandbox Code Playgroud)