• 被忽略的class“特殊成员”—转换函数


    一、operator的重载

    这个关键字平时其实几乎不怎么使用,但是偏偏这个关键字和new/delete之类比较孤僻的关键字还经常一起出现,使整个情况看起来更加诡异。之前应该是在看C++ stl库中看到过在类成员中自定义转换函数的,就是希望让一个对象可以转换为bool类型(通常用在if之类的逻辑表达式中)。当时在看到这个转换函数的时候,它还是和另一个孤僻的stl::tr1扩展中的functional在一起。大家知道,stl的function的理想是将一切(即使参数数量不同的)可以调用的东西(functor类、函数)封装为一个对外统一的函数指针形态。既然它的理想是函数,那么它最好能够判断出自己是否是一个空指针,也就是可以表现为bool的特征。
    注意下面类中两个operator的用法,看起来差不多,也比较容易混淆,但是它们是两种完全不同的语法/语义结构,一个是 conversion function,一个是 operator function。从语法的角度看,它们最明显的语法特征是一个operator关键字引导的开始,另一个是类型引导的开始。
    gcc-4.4.7libstdc++-v3include r1_implfunctional
    /// class function
    template<typename _Res, typename... _ArgTypes>
    class function<_Res(_ArgTypes...)>
    : public _Maybe_unary_or_binary_function<_Res, _ArgTypes...>,
    private _Function_base
    {
    ……
    // [3.7.2.3] function capacity

    /**
    * @brief Determine if the %function wrapper has a target.
    *
    * @return @c true when this %function object contains a target,
    * or @c false when it is empty.
    *
    * This function will not throw an exception.
    */
    operator _Safe_bool() const
    {
    if (_M_empty())
    return 0;
    else
    return &_Hidden_type::_M_bool;
    }

    // [3.7.2.4] function invocation

    /**
    * @brief Invokes the function targeted by @c *this.
    * @returns the result of the target.
    * @throws bad_function_call when @c !(bool)*this
    *
    * The function call operator invokes the target function object
    * stored by @c this.
    */
    _Res operator()(_ArgTypes... __args) const;
    ……
    }

    二、C++语法中对两种结构的说明

    在《C++11-draft-starndard.pdf》中,对于第二种引导的语法叫做"operator-function-id"
    operator-function-id:
    operator OPERATOR
    OPERATOR: one of
    new delete new[] delete[]
    + - * / % ˆ & | 
    ! = < > += -= *= /= %=
    ˆ= &= |= << >> >>= <<= == !=
    <= >= && || ++ -- , ->* ->
    ( ) [ ]
    而对于conversion-function-id语法的定义是这样的
    conversion-function-id:
    operator conversion-type-id
    单单从这两个语法结构看,它们都是以operator开始,但是在语法解析的时候,它们的差别还是很明显的。这里要从概念上明白,operator-function-id和普通的函数名一样,operator和操作符一起作为一个原子的语法单位,它的意义和普通的函数名称一样。直观上可以这么说:在可以出现普通函数的地方,把函数名替换为"operator + 操作符"都是可以的。
    tsecer@harry: cat -n operator.function.id.cpp
    1 struct A
    2 {
    3 int operator*(int x)
    4 {
    5 return x * x;
    6 }
    7 int mul(int x)
    8 {
    9 return x * x;
    10 }
    11 };
    12
    13 int operator+(const A &a, int x)
    14 {
    15 return x + x;
    16 }
    17 int add(const A &a, int x)
    18 {
    19 return x + x;
    20 }
    21
    22 A a;
    23 int main()
    24 {
    25 return a.operator*(1) + a.mul(1) + operator+(a, 1) + add(a, 1) ;
    26 }
    tsecer@harry: gcc -c operator.function.id.cpp
    tsecer@harry:
    看上面的代码片段,可以看到, “operator*” 和 “operator+”作为一个整体,分别与mul和add对应,它们在语法上是等价的,它们都是更高级的unqualified-id语法单位的类型,它们和字面的标识符identifier同一个级别,只是在词法上有不同的描述规则
    primary-expression:
    literal
    this
    ( expression )
    id-expression
    lambda-expression
    id-expression:
    unqualified-id
    qualified-id
    unqualified-id:
    identifier
    operator-function-id
    conversion-function-id
    literal-operator-id
    ~ class-name
    ~ decltype-specifier
    template-id

    三、C++对于conversion函数的说明

    从目录的组织上看,conversion是和constructor/destructor一样的特殊成员函数放在同一个目录中,只是ctor/dtor最为常用而掩盖了conversion,而不是conversion不配有姓名。它们他叔的原因在于它们其实都是没有使用类型声明引导。例如constructor的是通过和所在class的名字相同而被识别;destructor是由于开头的'~',而对应的conversion则需要使用开头的'operator'关键字来识别。
    12 Special member functions 256
    12.1 Constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
    12.2 Temporary objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
    12.3 Conversions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
    12.4 Destructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
    12.5 Free store . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266
    12.6 Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
    12.7 Construction and destruction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
    12.8 Copying and moving class objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
    12.9 Inheriting constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
    If a conversion function is a member function, the
    “12.3.2 Conversion functions”中对于转换函数的定义
    type of the conversion function (8.3.5) is “function taking no parameter returning conversion-type-id”.

    四、gcc对class特殊成员的处理

    这里只要注意到:gcc内部也的确是把conversion和ctor/dtor同时处理的,也就是说明这个被忽视的conversion的确是一个基础但是没有构造/析构那么高使用频率的特殊类成员。
    gcc-4.4.7gcccpparser.c
    static cp_declarator *
    cp_parser_direct_declarator (cp_parser* parser,
    cp_parser_declarator_kind dcl_kind,
    int* ctor_dtor_or_conv_p,
    bool member_p)
    {
    ……
    if (class_type)
    {
    if (TREE_CODE (unqualified_name) == BIT_NOT_EXPR)
    sfk = sfk_destructor;
    else if (IDENTIFIER_TYPENAME_P (unqualified_name))
    sfk = sfk_conversion;
    else if (/* There's no way to declare a constructor
    for an anonymous type, even if the type
    got a name for linkage purposes. */
    !TYPE_WAS_ANONYMOUS (class_type)
    && constructor_name_p (unqualified_name,
    class_type))
    {
    unqualified_name = constructor_name (class_type);
    sfk = sfk_constructor;
    }

    if (ctor_dtor_or_conv_p && sfk != sfk_none)
    *ctor_dtor_or_conv_p = -1;
    }
    ……
    }

    五、简单的使用例子

    注意这个例子中函数中的fun是一个对象而不是一个传统上指针
    tsecer@harry: cat function.cpp
    #include <tr1/functional>

    std::tr1::function<void()> fun;

    int main()
    {
    if (!fun)
    {
    return 123;
    }
    return 0;
    }
    tsecer@harry:
    tsecer@harry: cat -n function.cpp
    1 #include <tr1/functional>
    2
    3 std::tr1::function<void()> fun;
    4
    5 int main()
    6 {
    7 if (!fun)
    8 {
    9 return 123;
    10 }
    11 return 0;
    12 }
    tsecer@harry: g++ -g function.cpp
    tsecer@harry: ./a.out
    tsecer@harry: echo $?
    123
    tsecer@harry: gdb ./a.out
    GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7
    Copyright (C) 2013 Free Software Foundation, Inc.
    License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law. Type "show copying"
    and "show warranty" for details.
    This GDB was configured as "x86_64-redhat-linux-gnu".
    For bug reporting instructions, please see:
    <http://www.gnu.org/software/gdb/bugs/>...
    Reading symbols from /home/harry/study/cpp.rvalue/a.out...done.
    (gdb) b 7
    Breakpoint 1 at 0x400604: file function.cpp, line 7.
    (gdb) r
    Starting program: /home/harry/study/cpp.rvalue/./a.out

    Breakpoint 1, main () at function.cpp:7
    7 if (!fun)
    Missing separate debuginfos, use: debuginfo-install glibc-2.17-196.tl2.3.x86_64 libgcc-4.8.5-4.el7.x86_64 libstdc++-4.8.5-4.el7.x86_64
    (gdb) si
    0x0000000000400609 7 if (!fun)
    (gdb)
    std::tr1::function<void ()>::operator std::tr1::function<void ()>::_Hidden_type* std::tr1::function<void ()>::_Hidden_type::*() const (this=0x400510 <_start>) at /usr/include/c++/4.8.2/tr1/functional:2048
    2048 operator _Safe_bool() const
    (gdb) list
    2043 */
    2044 #if __cplusplus >= 201103L
    2045 explicit operator bool() const
    2046 { return !_M_empty(); }
    2047 #else
    2048 operator _Safe_bool() const
    2049 {
    2050 if (_M_empty())
    2051 return 0;
    2052 else
    (gdb) si
    0x000000000040070f 2048 operator _Safe_bool() const
    (gdb)
    0x0000000000400712 2048 operator _Safe_bool() const
    (gdb)
    0x0000000000400716 2048 operator _Safe_bool() const
    (gdb)
    2050 if (_M_empty())
    (gdb)
    0x000000000040071e 2050 if (_M_empty())
    (gdb)
    0x0000000000400721 2050 if (_M_empty())
    (gdb)
    std::tr1::_Function_base::_M_empty (this=0x4007ad <__libc_csu_init+77>) at /usr/include/c++/4.8.2/tr1/functional:1760
    1760 bool _M_empty() const { return !_M_manager; }
    (gdb) si
    0x00000000004006c7 1760 bool _M_empty() const { return !_M_manager; }
    (gdb) p _M_manager
    $1 = (std::tr1::_Function_base::_Manager_type) 0xc35f415e415d415c
    (gdb) s
    std::tr1::function<void ()>::operator std::tr1::function<void ()>::_Hidden_type* std::tr1::function<void ()>::_Hidden_type::*() const (this=0x601060 <fun>) at /usr/include/c++/4.8.2/tr1/functional:2051
    2051 return 0;
    (gdb) list
    2046 { return !_M_empty(); }
    2047 #else
    2048 operator _Safe_bool() const
    2049 {
    2050 if (_M_empty())
    2051 return 0;
    2052 else
    2053 return &_Hidden_type::_M_bool;
    2054 }
    2055 #endif
    (gdb) si
    0x0000000000400731 2051 return 0;
    (gdb) p _M_manager
    $2 = (std::tr1::_Function_base::_Manager_type) 0x0
    (gdb)

  • 相关阅读:
    C#:类的学习
    SilverLight xaml页面跳转方法
    C#对象生命周期(转载)
    jquery常用的方法
    关于Java日期简单应用
    Compilingphpwithcrypt()
    SSI使用详解
    实例解析:从IIS的ASP迁移到APACHE的PHP
    三步搞定phpwind的静态化部署
    Informix Dynamic Server 中的分布式事务
  • 原文地址:https://www.cnblogs.com/tsecer/p/12558312.html
Copyright © 2020-2023  润新知