为啥我的两个元组包含字符串,以相同的方式创建,不相等?

Posted

技术标签:

【中文标题】为啥我的两个元组包含字符串,以相同的方式创建,不相等?【英文标题】:Why are my two tuples containing strings, created the same way, not equal?为什么我的两个元组包含字符串,以相同的方式创建,不相等? 【发布时间】:2020-12-22 17:41:49 【问题描述】:

我正在使用 Microsoft Visual C++ 编译以下程序,作为 C++20 程序:

#include <iostream>
#include <tuple>

int main()

    auto t1 = std::make_tuple("one", "two", "three");
    auto t2 = std::make_tuple("one", "two", "three");
    
    std::cout << "(t1 == t2) is " << std::boolalpha << (t1 == t2) << "\n";
    std::cout << "(t1 != t2) is " << std::boolalpha << (t1 != t2) << "\n";

    return 0;

当我运行它时,我看到以下输出:

(t1 == t2) is false
(t1 != t2) is true

元组是相同的,为什么会出现错误的比较结果?我该如何解决这个问题?

【问题讨论】:

【参考方案1】:

auto 并不总是你的朋友。我认为在没有样板的情况下可靠地获得“正确”行为的正确方法是显式使用您知道具有值相等性的类型。然后你也可以省略make_tuple 并简单地使用initialiser-list 构造函数:

#include <string>
#include <tuple>
#include <iostream>

typedef std::tuple<std::string, std::string, std::string> StrTriple;

int main() 
  
  StrTriple t1"one", "two", "three";
  StrTriple t2"one", "two", "three";

  std::cout << "(t1 == t2) is " << std::boolalpha << (t1 == t2) << "\n";
  std::cout << "(t1 != t2) is " << std::boolalpha << (t1 != t2) << "\n";

    return 0;

毫无疑问,有些人会争辩说std::string 的内存管理会产生不必要的开销。 string_view 可能更可取,但是在实际应用程序中,字符串可能需要在某个地方动态分配。

【讨论】:

std::tuple 确实具有价值平等。不幸的是,比较的值并不是您想要比较的值...... @Deduplicator std::tuple 不是类型,所以说它具有值相等是没有意义的。 tuple&lt;string,string,string&gt; 有,tuple&lt;char*,char*,char*&gt; 没有——这两者都是类型,而std::tuple 本身只是一个类型构造函数 好的,更明确一点:std::tuple 如果所有参数都具有值相等性,则具有值相等性。只是您实际上并不想比较参数的值,而是它们指向的字符串的值。【参考方案2】:

您正在比较指向字符缓冲区的指针,而不是字符串。

有时编译器会将两个不同的"one"s 转入同一个缓冲区,有时则不会。

在你的情况下,它不是。可能是调试版本。

添加#include &lt;string_view&gt;,然后

using namespace std::literals;

auto t1 = std::make_tuple("one"sv, "two"sv, "three"sv);
auto t2 = std::make_tuple("one"sv, "two"sv, "three"sv);

你会得到你所期望的。 (在c++17 之前的编译器中,使用&lt;string&gt;""s 而不是&lt;string_view&gt;""sv)。

【讨论】:

我不禁想到这里故事的寓意是“如果您不知道自己分配的是什么类型,请不要使用auto。” @chep 相反,"" 是 C 遗留字符串,真的很烦人。两个文本相同的文字在实现中定义为相等的事实是荒谬的。 @Yakk-AdamNevraumont 如果在同一个 TU 中,您可以提出强制合并它们的理由,但超出此范围很容易变得昂贵。走另一条路会导致臃肿。 @Deduplicator 不,问题是"" 是一个数组文字,而数组文字上的== 衰减为指针并比较指针,这在C++ 中都是遗留的C 垃圾。合并字符串是一条红鲱鱼; "hello" 的地址应该与7 的地址一样重要。 Decay-to-pointer 在 C 中发明时是一种 hack,不比较 == 的数组文字是一个缺失的特性;当他们知道后果时,没有人会用一种语言写出来。为了向后兼容,我们坚持使用它。 @Yakk 并不是说​​我们在这里有 == 和两个字符串文字,但是是的,将两个参数衰减到二元运算符有点多。如果数组是一流的就好了,是的,std::array 只是一个粗略的创可贴。这也会将 array decay 更改为只是另一个标准转换,可能强制用于非模板 vararg。【参考方案3】:

这个问题与 C++20 无关,而是来自于字符串字面量的实现方式。答案例如在这里:

Why do (only) some compilers use the same address for identical string literals?

简而言之,您的程序属于“undefined 未指定行为”类别,因为它假定相同的 C 样式字符串文字具有相同的地址。这是因为像"a" == "a" 这样的表达式比较的是地址,而不是内容。如果您使用 std::string 文字,例如 "one"s"one"sv 等,您的代码可以变得安全且可预测,请参阅 https://en.cppreference.com/w/cpp/string/basic_string/operator%22%22s

【讨论】:

我怀疑 OP 打算比较字符串地址...【参考方案4】:

"one" 的类型是什么?这不是字符串,而是字符串文字。

你的问题基本上归结为这段代码:

char const* a = "one";
char const* b = "one";

std::cout << "(a == b) is " << std::boolalpha << (a == b) << "\n";
std::cout << "(a != b) is " << std::boolalpha << (a != b) << "\n";

最有可能输出相同的结果。

这是因为字符串文字会衰减为char const*。比较两个指针会比较它们在内存中的位置。现在这是您的编译器是否将字符串文字折叠成一个的问题。如果字符串文字被折叠,那么它们将相等,如果它们不是,它们将不相等。这可能因优化级别不同而有所不同。

那么你如何修正你的比较呢?

最好使用std::string_view,因为您似乎不需要拥有或更改其内容:

using namespace std::literals;

// ... 

auto t1 = std::make_tuple("one"sv, "two"sv, "three"sv);
auto t2 = std::make_tuple("one"sv, "two"sv, "three"sv);

std::string_view 类是指针和大小的薄包装,并定义了一个检查值相等性的比较运算符。

【讨论】:

我很惊讶地看到,即使使用gcc -fno-merge-constants 编译,我也得到了“(a == b) 为真”,(与元组相同)。猜猜这个标志更像是一个建议而不是一个要求。

以上是关于为啥我的两个元组包含字符串,以相同的方式创建,不相等?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 JavaScript 会根据结构相同的字符串猜测两个不同的时区?

为啥我的输入被插入到两个具有相同索引的 diff 数组中? (js)

计算机程序中有些表达式为啥有括号呢?

为啥我的 CSV 比具有相同数据的 JSON 文件大?

为啥 Spark 以不同的方式解释这两个查询?

为啥我的排列算法对所有排列都给出相同的结果?