C++ 可选值<;地图<;字符串,字符串>&燃气轮机;获得;“腐败”;在非常特殊的情况下
很抱歉这个糟糕的标题,但我看到的是奇怪的,很难简明扼要地解释 基本上,我们的代码中有一个C++ 可选值<;地图<;字符串,字符串>&燃气轮机;获得;“腐败”;在非常特殊的情况下,c++,C++,很抱歉这个糟糕的标题,但我看到的是奇怪的,很难简明扼要地解释 基本上,我们的代码中有一个可选的,通过getter/setter访问,有时当我们检查值时,会得到非常奇怪的结果。以下是重现问题的简化代码: #include <optional> #include <map> #include <iostream> using namespace std; optional<map<string, string>> optmap; st
可选的
,通过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;
}
}
请注意,在最后一种情况下,这些值只会被“损坏”,在这种情况下,我们使用(auto&item:get().value())进行迭代。更奇怪的是,这种情况似乎只发生在一定长度的字符串上。如果值的长度小于16个字符,则没有问题。如果我将地图更改为包含以下内容:
sample.emplace("fifteencharokay", "15");
sample.emplace("sixteencharweird", "16");
我得到这个输出:
$ ./a.out
sample:
fifteencharokay, 15
sixteencharweird, 16
result:
fifteencharokay, 15
sixteencharweird, 16
function call:
fifteencharokay, 15
sixteencharweird, 16
inline iteration:
fifteencharokay, 15
harweird, 16
(请注意,“sixteencharweird”
在最后一行被截断为“harweird”
)
这里发生了什么?为什么在这个非常特殊的情况下(长字符串和直接迭代函数调用的结果)会出现问题?是否有某种C++规则,我在这里通过这种方式来打破?在这个循环中:
for (auto &item : Get().value())
您正在调用未定义的行为,因为由Get()
返回的临时值将在完整表达式的末尾消失,而循环范围将迭代的.value()
引用的内存不再存在
对于长度小于16个字符的字符串,您会注意到奇怪的行为,这可能是由于小字符串优化造成的。由于字符串保留短字符串的内部缓冲区,因此仍然可以在那里看到内存。当然,这仍然是UB,你不能依赖它
您可以通过执行以下操作来解决此问题:
auto const &g = Get();
for (auto &item : g.value())
这是一个例子
事实上,c++20为with initializer construct添加范围正是为了这个目的:
for (auto const &g = Get(); auto &item : g.value())
有趣。实际上,我从你的代码中得到了一个segfault,它运行在Mac上,使用的是Clang的标准库。您使用的是哪种库实现?这是由以下内容修复的:
auto&&x=Get();对于(auto&&item:x.value())
。这似乎是一个终生的问题。string
的常见实现在内部存储最多16个字符的字符串,在外部存储更长的字符串。我不知道这是否能解释你16个字符的缺陷?@VladFeinstein是的,很好。我将把它添加到我的答案中。我相信使用基于范围的for循环的临时变量是允许的,因为基于范围的for循环会产生一个右值引用并延长其生命周期。它在这里不起作用的原因是被保留的是value()
的结果,它引用了不存在的Get()
的结果。如果Get()
返回的是map
的副本,而不是可选的
,则不需要.value()
,循环应该可以。@FrançoisAndrieux是的,稍微编辑一下,是否更清晰?这看起来不错,但我会留下我的评论作为额外的澄清。既然这不是一种罕见的反模式,有没有办法让编译器对此发出警告?@TEDLYNGOM好吧,它在这种特殊情况下可能没有用处,但一般来说这样做是一种好的做法,所以我将其保留在:)
for (auto const &g = Get(); auto &item : g.value())