从operator new看C++操作符重载

一、gcc对于new operator的说明

主要是说明:new操作在语法逻辑上看是在new之后一定要看到“类型”的,举个简单的例子:字面量100是一个常量而不是类型,所以语法 new (100)是语法错误的。

/* Parse a new-expression.

new-expression:
:: [opt] new new-placement [opt] new-type-id new-initializer [opt]
:: [opt] new new-placement [opt] ( type-id ) new-initializer [opt]

Returns a representation of the expression. */

static tree
cp_parser_new_expression (cp_parser* parser)

二、new operator对应的语义

从语义上看,主要是计算出类型的大小,然后调用operator new函数。
static tree
build_new_1 (vec<tree, va_gc> **placement, tree type, tree nelts,
vec<tree, va_gc> **init, bool globally_qualified_p,
tsubst_flags_t complain)
{
……
if (!globally_qualified_p
&& CLASS_TYPE_P (elt_type)
&& (array_p
? TYPE_HAS_ARRAY_NEW_OPERATOR (elt_type)
: TYPE_HAS_NEW_OPERATOR (elt_type)))
{
……
}
alloc_call = build_new_method_call (build_dummy_object (elt_type),
fns, placement,
/*conversion_path=*/NULL_TREE,
LOOKUP_NORMAL,
&alloc_fn,
complain);
}
else
{
/* Use a global operator new. */
/* See if a cookie might be required. */
if (!(array_p && TYPE_VEC_NEW_USES_COOKIE (elt_type)))
{
cookie_size = NULL_TREE;
/* No size arithmetic necessary, so the size check is
not needed. */
if (outer_nelts_check != NULL && inner_size.is_one ())
outer_nelts_check = NULL_TREE;
}

alloc_call = build_operator_new_call (fnname, placement,
&size, &cookie_size,
outer_nelts_check,
&alloc_fn, complain);
}
……
}

三、gcc如何表示一个operator

在gcc内部,查找operator new就是查找类型名称为“operator new”的函数,这个是预定义的类型。
static tree
cp_parser_operator (cp_parser* parser)
{
tree id = NULL_TREE;
cp_token *token;

/* Peek at the next token. */
token = cp_lexer_peek_token (parser->lexer);
/* Figure out which operator we have. */
switch (token->type)
{
……
case CPP_MULT:
id = ansi_opname (MULT_EXPR);
break;
……
/* If we have selected an identifier, we need to consume the
operator token. */
if (id)
cp_lexer_consume_token (parser->lexer);
/* Otherwise, no valid operator name was present. */
else
{
cp_parser_error (parser, "expected operator");
id = error_mark_node;
}

return id;
}

其定义为
#define ansi_opname(CODE) \
(operator_name_info[(int) (CODE)].identifier)

名字的初始化

static void
init_operators (void)
{
tree identifier;
char buffer[256];
struct operator_name_info_t *oni;

#define DEF_OPERATOR(NAME, CODE, MANGLING, ARITY, ASSN_P) \
sprintf (buffer, ISALPHA (NAME[0]) ? "operator %s" : "operator%s", NAME); \
identifier = get_identifier (buffer); \
IDENTIFIER_OPNAME_P (identifier) = 1; \
\
oni = (ASSN_P \
? &assignment_operator_name_info[(int) CODE] \
: &operator_name_info[(int) CODE]); \
oni->identifier = identifier; \
oni->name = NAME; \
oni->mangled_name = MANGLING; \
oni->arity = ARITY;

#include "operators.def"
#undef DEF_OPERATOR

gcc-4.8.2\gcc\cp\operators.def
#define DEF_SIMPLE_OPERATOR(NAME, CODE, MANGLING, ARITY) \
DEF_OPERATOR(NAME, CODE, MANGLING, ARITY, 0)
……
/* Memory allocation operators. */
DEF_SIMPLE_OPERATOR ("new", NEW_EXPR, "nw", -1)
所以综合的结果就是,这些函数在内部得标准名称(ansi)名称就是sprintf (buffer, ISALPHA (NAME[0]) ? "operator %s" : "operator%s", NAME); 格式化出来的名字,operator new的名称就是这个“operator new”,而他的mangled name为nw。

四、举栗子


可以看到,new是一个语法单位,而operator new作为一个整体更像是一个有约定语义的特殊函数名,可以用operator new来动态申请内存,从而可以替换掉对C库malloc的直接调用。
tsecer@harry: cat -n operator.new.cpp
1 typedef unsigned long int size_t;
2
3
4 void * malloc(size_t size)
5 {
6 return (void*)0;
7 }
8
9 void * operator new(size_t size)
10 {
11 return (void*)malloc;
12 }
13
14 void* foo(int size)
15 {
16 return new (size);
17 }
18
19 void* bar(int size)
20 {
21 return operator new (size);
22 }
tsecer@harry: g++ -c operator.new.cpp
operator.new.cpp: 在函数‘void* foo(int)’中:
operator.new.cpp:16:19: 错误:expected type-specifier before ‘;’ token
return new (size);
^
tsecer@harry:

五、何时查找操作符重载

在c++标准中说明,算符重载时,必须至少有一个操作数是非内置基本类型
An operator function shall either be a non-static member function or be a non-member function and have at least one parameter whose type is a class, a reference to a class, an enumeration, or a reference to an enumeration.
这意味着,在声明的时候如果操作数均为内置类型,则语法上是非法的,否则可以通过重载算符来改变内置类型(例如 5+5的语义),这样任何人之间基本的信任都没有了。
tsecer@harry: cat -n overload.add.cpp
1 int operator + (int x, int y)
2 {
3 return x * y;
4 }
tsecer@harry: LC_ALL=C g++ -c overload.add.cpp
overload.add.cpp:1:29: error: 'int operator+(int, int)' must have an argument of class or enumerated type
int operator + (int x, int y)
^
tsecer@harry
在gcc内部,生成一个新的表达式的函数为build_new_op_1,从函数实现可以看到,是将算符转换为了字符串名称的函数,然后根据第一个参数类型决定是否到第一个参数类型内部查找,然后添加外层声明的操作符,最后加上内置操作符类型。
static tree
build_new_op_1 (location_t loc, enum tree_code code, int flags, tree arg1,
tree arg2, tree arg3, tree *overload, tsubst_flags_t complain)
{
……
if (code == MODIFY_EXPR)
{
code2 = TREE_CODE (arg3);
arg3 = NULL_TREE;
fnname = ansi_assopname (code2);
}
else
fnname = ansi_opname (code);
……
/* Add namespace-scope operators to the list of functions to
consider. */
add_candidates (lookup_function_nonclass (fnname, arglist, /*block_p=*/true),
NULL_TREE, arglist, NULL_TREE,
NULL_TREE, false, NULL_TREE, NULL_TREE,
flags, &candidates, complain);
……
/* Add class-member operators to the candidate set. */
if (CLASS_TYPE_P (TREE_TYPE (arg1)))
……

add_builtin_candidates (&candidates, code, code2, fnname, args,
flags, complain);
……

}

posted on 2020-06-08 20:40  tsecer  阅读(611)  评论(0编辑  收藏  举报

导航