在“new int;”中的“new”被认为是一个运算符吗?

85
int * x = new int;中,new int;是一个new expression. 术语“new operator”似乎可与“new expression”交替使用,例如在这个问题中:Difference between 'new operator' and 'operator new'? 关键字new在新表达式中的用法是否可以说是一个运算符?为什么或为什么不?
如果不是,是否有另一个称呼新表达式为“new operator”的理由?
我很难找到构成运算符的权威定义。
我已经了解了operator new和“new expression”的区别,其中operator new为对象分配内存,“new expression”可能最终调用operator new。

4
@Rogue - 问题在于,new 就像这里的 sizeof。我认为大多数 C++ 社区都将 sizeof 视为运算符。标准甚至在描述其效果时提到了 "sizeof 运算符"。 - StoryTeller - Unslander Monica
2
@StoryTeller-UnslanderMonica,继续我的第二条评论(现在已移至答案),关于sizeof,我认为它是一个表达式sizeof(<type>)的运算符。而+是一元表达式<type> <op> <type>的运算符。这是我的理解,如有不对之处请指正。 - Rogue
2
@Rogue - 的确如此。这就是为什么我会认为规范文本在将其中一个称为运算符时而不是另一个时相当武断的原因。 - StoryTeller - Unslander Monica
1
对于被接受的答案,有一个附注:无论 new 是否是运算符,表达式和运算符之间存在差异。例如,new int 是一个表达式,因为它会被计算出来。如果认为 new 是一个运算符,那么 new int 中只有 "new" 部分是运算符(即它是一个独立的东西)。从概念上讲,运算符是作用于其他东西的东西。 - Filip Milovanović
6个回答

143
在 "new int" 中的 "new" 不被认为是一个运算符,也不被认为是 "非" 运算符。 C++标准对于什么构成 "运算符" 的定义非常模糊,并且甚至存在不一致性。在列出运算符(在词法分析和预处理期间定义)时,它将它们与“标点符号”(例如 ()一起列出,但从未给出有关标点符号的一般规则。它将 "new" 列为关键字和运算符。它将 "sizeof" 列入关键字集合但不列入运算符集合中,但稍后又将其称为运算符。 这里的要点是,C++标准委员会并不过分关注将词汇世界分为“运算符”和“非运算符”。这是因为没有真正需要这样做的必要。没有适用于所有运算符或所有非运算符的语法规则。重要的集合,如可重载运算符集,是单独给出的;像二元算术表达式之类的语法规则是逐个给出的。 基本上,“运算符”不是一种对C++语言来说有形式上意义的范畴,因此任何分类的答案都会基于它的“感觉”。你可以把它称为运算符,但你也可以把它称为关键字(或标点符号!),语言标准不会与你意见相左。

6
C和C++标准在很多术语方面都不够严谨,因为它们预计读者如果存在缺失或歧义会自行推断意思,而不是为了避免读者利用这些漏洞来进行“优化”而刻意禁止那些标准作者认为毫无必要反复强调的荒谬情况。 - supercat
标准在人类描述方面可能有些模糊,但是有关键字“operator”,我想当有人要求精确划分时,使用英语单词“operator”来表示与该关键字一起工作的内容是公平的。在这种意义上,“new”、“new[]”、“delete”、“delete[]”、“co_await”、某些类型转换以及将用户定义的文字量转换为对象的操作符。 - hemflit
@hemflit 运算符早于 operator 关键字,其中包括一些无法与 operator 关键字一起使用的内容,例如 .。如果我被逼枪下给出“运算符”的定义,我的脑海中不会想到“可重载运算符”。 - Sneftel
@Sneftel使用含糊不清的英语单词“operator”早于operator关键字,是的。如果我被迫提出绝对精确的界定什么是或不是运算符,我会选择语法中精确定义的一件事。在日常交流中,我并不总是需要那么高的精度。 - hemflit
我愿意承认这是一个准确的定义,但它并不是一个好的定义。 - Sneftel

16
new-expression中使用的关键字new是否可以称为运算符?为什么或者为什么不是?
不是。在new-expression中的new是一个标识new-expression的关键字。 new-expression通过调用operator newoperator new[]来获取存储空间。它还会初始化该存储空间,并在初始化引发异常时使用operator deleteoperator delete[]释放它。
有一个明显的区别,即operator new仅被称为可重载的用户可替换函数,并且new-expression不仅调用此函数。
参考:7.6.2.8/10 [expr.new] new-expression可以通过调用分配函数([basic.stc.dynamic.allocation])来获取对象的存储空间。如果new-expression通过抛出异常而终止,则可以通过调用解除分配函数来释放存储空间。如果分配的类型是非数组类型,则分配函数的名称为operator new,解除分配函数的名称为operator delete。如果分配的类型是数组类型,则分配函数的名称是operator new[],解除分配函数的名称是operator delete[]
考虑反例,我们定义了两个函数:operator newoperator new[],并使用它们来获取存储空间。
T operator+(T, T);
void* T::operator new(std::size_t);

对于某些类型T,两种形式的加法如下:

T a, b;
T c = a + b;
T d = T::operator +(a, b);

相同。中缀符号只是操作符调用的语法糖。

然而,分配行为非常不同:

T *p = new T;
// does much more than
void *v = T::operator new(sizeof(T));

因此,称new-expression为对operator new的调用是不合理的。因此,new关键字并不仅仅是选择要调用的函数。否则,它将不得不提及可能被调用的operator delete函数。


2
operator new是一个可重载的运算符” - 真的吗?我不这么认为:operator + 不是一个可重载的运算符 - + 才是,你通过一个名为 operator +函数来定义重载。 - Konrad Rudolph
1
不,我的回答是指出运算符是作为 new 表达式的一部分调用的特定函数。new 表达式是由关键字标识的表达式,并且它会找到适当的运算符进行调用。 - Useless
1
我仍然没有看到这如何解决new关键字在新表达式中出现的状态问题。 - John Bollinger
6
实际上,“new” 可以进行运算符重载,这表明答案应该是“是”,“new”(它本身)是一个运算符。 - John Bollinger
2
标准仅涉及同时调用运算符的表达式。如果没有递归,它们不能是同一件事,因此简洁的解释是表达式和运算符正是标准所说的。 - Useless
显示剩余4条评论

12

为避免和只分配内存的void* operator new (std::size_t count)混淆,我只会称其为新表达式。在新表达式中,会涉及到分配内存、开始生存周期、调用构造函数等过程。

new的问题在于它不仅仅是调用operator new。这有点令人困惑,因为对于x+y+只会调用operator+


9
这由[expr.new]控制,它清楚地区分了new表达式和通过调用内存分配函数(名为operator new)获取内存的方法。特别地,在[expr.new]/8中指出:

使用new表达式可以通过调用内存分配函数([basic.stc.dynamic.allocation]),为对象获得内存。 如果新表达式因抛出异常而终止,则可以通过调用解除分配函数释放存储空间。 如果配置的类型是非数组类型,则内存分配函数的名称为operator new,解除分配函数的名称为 operator delete。 如果是数组类型,则内存分配函数的名称为operator new[],解除分配函数的名称为 operator delete[]


正确说法是在新表达式中使用的关键字new是操作符吗?

特别地,在示例中,虽然非规范化,但[expr.new]/4如下描述该函数为操作符函数:"[...] the new operator":

[...] Instead, the explicitly parenthesized version of the new operator can be used [...]


3
谢谢回答。从我收到的回答数量来看,似乎没有严格的语言可以回答这个问题,因此非规范性文本可能成为最好的证据。 - François Andrieux

8

第一.

实际上,new int 中的 new 被认为是一个关键字,而不是运算符(详见 [lex.key/1])。虽然有人认为 new 是运算符,但这种观点与标准大多数人的观点并不一致。

第二.

在语言中,[lex.operators/1] 列出了运算符。不要被误导以为这些只是“预处理运算符”。在词法运算符的意义下,不存在这样的区别。这也是没有意义的,因为您不能对宏进行例如 ++ 的操作。

第三.

关于 new-expression,这里的问题变得有点相对模糊了。例如,在[expr.new/4] 中有以下措辞:

相反,可以使用显式括号版本的 new 运算符来创建复合类型的对象

我认为这是编辑错误,因为它与上面提供的定义不一致,并且在该部分中没有出现。

第四.

最后,让我们来看看 运算符重载。在运算符声明的语法产生式中,列出了所有运算符(包括new)的终结符号称为运算符 (见[over.oper.general/1])。 我们不需要担心这个问题。语法中终结符的命名从未旨在引入术语定义。毕竟,您有and-expression,它不需要是位运算 AND 操作; 它只能是一个 equality-expression

and-expression:
   equality-expression
   and-expression & equality-expression

像这样定义语法是很常见的,它肯定不意味着每个equality-expression都应该被认为是位运算 AND 操作的调用。

第五.

最后,有些人声称运算符重载部分中的以下措辞证明了 new 现在以某种方式独立地、神奇地成为运算符:

运算符 new[], delete[], ()[] 是由多个标记组成的。
对于它们,我想说的是,不仅仅是 new 没有列出来,而且这里明显是在更广泛的意义上使用术语“运算符”,即“可以被重载的东西”,尽管 new 本身 仍然不是一个运算符。它还是一个非规范性注释,这应该告诉你所有你需要知道的信息。
而且,正如你自己指出的,我们已经认为 operator new 是另一回事了。

2
[lex.operators] 显然是不完整的(也并非旨在完整!),因为 [expr.sizeof] 规范明确表示 sizeof 也是一个运算符。关于 throw,[expr.throw] 不太明确,但它仍然谈到了 throw 的“操作数”,这意味着 throw 是一个运算符。 - Konrad Rudolph

7
虽然我知道你在寻找一个语义化的答案,但就我的观点而言,我会说它是一个“关键字”(因为它显然是保留的),而我认为我们都知道它只是C++内存分配的构造。话虽如此,当我想到C++的“运算符”时,我倾向于认为它们是具有一组输入/输出的函数,可以针对特定类型进行重载。 new对所有类型都是相同的(更令人困惑的是,它可以被覆盖)。
不确定cppreference有多正式,但是:
新表达式通过调用适当的分配函数来分配存储。如果类型是非数组类型,则函数的名称为operator new。如果类型是数组类型,则函数的名称为operator new[]。

https://en.cppreference.com/w/cpp/language/new

看起来,new <type>是一个分配内存的表达式,而new则是该表达式的运算符。

按照同样的逻辑:

  • sizeof是表达式sizeof(<type>)的"运算符"(尽管sizeof是一种"编译时"运算符,与我们所熟悉的有些不同)
  • +是表达式<type> <operator> <type>的运算符
  • throw是表达式throw <throwaable>的运算符

按照这个逻辑,[]()也是一个运算符吗?for呢?throw呢? - Konrad Rudolph
1
@KonradRudolph 是的,我会说 []() 构成了不同的运算符。再次强调,for 绝对是一个关键字,但我认为区分它作为运算符的一部分是能够被覆盖的第一部分。从技术上讲,sizeof 不是运算符,这在 C++ 中更像是预处理表达式。而 throw 是一个表达式的运算符 throw <throwable> - Rogue
1
啊,“[]()”作为一个_lambda_在定义时很棘手(具体来说是因为我对C++不太熟悉)。所以,在这里我仍然会说,[] ()构成了lambda表达式的不同运算符,但我不确定你是否会认为它们不能被重载,或者实现/声明lambda本身就构成了基本lambda的重载(就像Java中一样)。在这里,语言设计开始变得有点艺术而非科学。至于“( … )”,在我使用过的大多数语言中,它都是一个分隔符,但我会快速看一下R语言。 - Rogue
你关于sizeof是正确的,已经修复了。对于 R 来说,它似乎将 (a...b) 括号表达式作为 a...b 的函数进行评估,并输出它们的计算结果?同样,我要说一些语言不会区分这个是否为运算符,因为它在系统中不需要区分任何类型。 - Rogue
2
顺便提一下,在R中,所有东西都是函数,一切都可以重新定义。没有全面的规范参考,我也不确定括号是否被认为是运算符,但它们可以被重新定义;实际上,表达式(a + b)在语义上与函数调用`(`(a + b)相同,或者确切地说,`(`(`+`(a, b)),其中`x`可用于使名称x在语法上符合函数调用而不是特殊字符(例如中缀运算符)。 - Konrad Rudolph
显示剩余2条评论

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