C++中条件运算符(?:)是如何工作的?

12

我写了下面的代码片段:

#include <string>

int main() {
    std::string str = "test";
    (str == "tes") ? str.replace(0, 1, "T") : 0;
}

(请点击此处查看)

不幸的是,它会导致一个逻辑错误

terminate called after throwing an instance of 'std::logic_error'
what():  basic_string::_S_construct NULL not valid

我想知道编译器为什么要构建一个字符串对象?


8
这是一个“表达式”。您应该将其用于返回一个值,而不是作为“if”语句。两个可能的“值”必须是相同的类型。 - Galik
7
很遗憾,@Galik,由于std::string的表达式采用了const char*构造函数,它们是相同类型,只是它认为正在从空指针构造字符串(这就是引发错误的原因)。 - Alex Huszagh
1
@Galik @Alex 是正确的。你所提到的问题应该会导致编译器错误,而不是运行时失败。 - user0042
2
@AlexanderHuszagh 请考虑将其发布为答案。该问题包含[MCVE](带有一些非常微小和琐碎的添加),并且是有效的。 - user0042
1
@Alex,我们为您提供了一个温暖舒适的床来回答问题。先生,还需要更多的靠垫或被子吗? - user0042
显示剩余6条评论
2个回答

17
三元运算符实际上的工作方式如下所示:
std::string str = "test";
std::string _;    // using _ since you don't store the variable
if (str == "tes") {
    _ = str.replace(0, 1, "T");
} else {
    _ = 0;  // this calls std::string(nullptr);
}

在上述情况中,您不是存储值,但有几个条件必须注意:
  1. 真值和假值必须是相同的类型(或可以转换为相同的类型)。
  2. 即使类型没有默认构造函数,它仍然可以正常工作(因此比上面更加复杂)。
问题在于您的代码期望类型为`std::string`,基于真实情况中的类型。虚假情况中的类型是一个字面量,该字面量可以被视为等效于`NULL`,因此可以被视为可转换为`std::string`的`const char*`。 如果尝试从`nullptr`构造一个`std::string`,将会抛出上述异常。
这实际上非常微妙,因为如果使用除0以外的任何整数字面量,编译器都会抛出错误:
#include <string>


int main()
{
    std::string s(0);           // 0 is comparable to NULL
//    std::string s1(1);        // compiler error: no constructor found
    return 0;
}

注意隐式转换。空值检查和运行时错误非常优雅,可以避免后续出现微妙的错误或崩溃(几乎肯定是段错误)。


2
你的例子中 "_" 的类型相当复杂,也许是这个问题的关键。 - Drew Dormann
3
0可以被隐式转换为NULL,因此被视为指针这一事实,大多数是来自C的遗留问题,过去它已经引起了很多微妙的错误。隐式转换可能非常棘手,发生错误是处理此类意外行为的一种优雅方式。 - Alex Huszagh
1
(我相信你已经知道了)在 C++ 中,应该始终努力避免使用指针。我发现依赖空对象或终止器要更好,而且非常容易,它们从同一基类继承(或是基类),但只有空成员函数。 - Clearer

0

所以,我想知道编译器构造字符串对象的原因是什么?

因为std::string提供了一个隐式构造函数,它接受一个普通的const char*,期望一个以NUL结尾的C风格字符串。

在使用该构造函数时进行了运行时断言,确保没有传递nullptr== 0),因此您看到了异常。


顺便提一下:

你的陈述

(str == "tes") ? str.replace(0, 1, "T") : 0;

可能会被良好的编译器优化掉,因为它没有任何重要的副作用。
因此,您看到的异常可能会在使用不同优化级别的构建中消失。


请注意:“真值和假值必须是相同类型(或可转换为相同类型)”这一点,假值必须能够转换为真值的类型,而编译器不能检查两者并决定哪个更好。 - SoronelHaetir
2
@Soronel,你错了。(除非我误解了你。)(0?0:0L)的类型是long。 - prl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接