六一的部落格


关关难过关关过,前路漫漫亦灿灿。



用户定义的类型转换 user-defined conversions :

  1. 类的转换构造函数
  2. 重载类型转换运算符

转换构造函数和类型转换运算符共同定义了类类型转换 class-type conversions

必须定义为成员函数, 将本类型转换为目的类型


类型转换运算符

conversion operator

形如

1operator T() const;

将本类型转换为类型T, T不能为void

返回目的类型的值

和函数返回类型一样, T也不能是数组或函数, 可以是函数指针, 数组指针和引用

不能声明返回类型, 形参列表必须为空 : 源类型为自身, 目的类型在函数名中给出

类型转换运算符通常不应该改变待转换对象的内容, 一般被定义为const


示例

 1class SmallInt
 2{
 3public:
 4    SmallInt(int i = 0) : val(i)
 5    {
 6        if (i < 0 || i > 255)
 7            throw out_of_range("Bad SmallInt value");
 8    }
 9    operator int() const { return val; }
10private:
11    size_t val;
12};
13
14
15SmallInt si;
16si = 4;                // 使用转换构造函数将4转换SmallInt对象, 赋值给si
17si + 3;                // si被转换成int

编译器一次只能执行一个用户定义的类型转换

编译器一次只能执行一个用户定义的类型转换, 但隐式的类类型转换可以在内置转换之前或之后, 与其一起使用

1SmallInt si = 3.14;            // 只使用内置类型转换, 将double转换为int
2si + 3.14;                     // si转换成int(用户定义类型转换),int转换成double(内置类型转换)

SmallInt的转换构造函数接受所有算术类型

1SmallInt(int i = 0);

实际编写代码时, 较少为类定义类型转换运算符

一个常见的类型转换运算符需求: 目的类型为bool


显式的类型转换运算符

explicit conversion operator

  1. 可通过static_cast使用

     1class SmallInt
     2{
     3public:
     4    SmallInt(int i = 0) : val(i)
     5    {
     6        if (i < 0 || i > 255)
     7            throw out_of_range("Bad SmallInt value");
     8    }
     9    explicit operator int() const { return val; }
    10private:
    11    size_t val;
    12};
    13
    14SmallInt si = 3;           // 转换构造函数, 非显式
    15si + 3;                    // 错误: 类型转换运算符要求显式
    16static_cast<int>(si) + 3;  // 正确
    
  2. 如果表达式(变量)被用作条件,编译器会将显式的类型转换自动应用于它

    condition
    if、while和do while的条件部分
    for的条件表达式
    逻辑非 ! ,逻辑或,逻辑与的运算对象 &&
    条件运算符 ? :

将目的类型为bool的类型转换运算符声明为显式

  1. 移位操作

    如果cin向bool的类型转换非隐式, 编译器会将cin转换为bool, 并提升为int, 然后左移42位

    1int i = 42;
    2cin << i;
  2. IO类型提供向bool的显式类型转换

    • 输入运算符返回左操作数, 在while的条件部分发生向bool的显式类型转换
    • 如果cin的条件状态为good, 函数返回true; false
    1while (cin >> value)
    2{
    3    // 处理
    4}

避免有二义性的类型转换

确保类类型和目的类型之间只存在唯一一种转换方式


第一种情形: 多重转换路径

一个类定义了转换构造函数, 而另一个类重载了类型转换运算符

  1. 类A: 定义了B到A转换构造函数
    1struct B;
    2struct A
    3{
    4    A = default;
    5    A(const B &);
    6};
  2. 类B: 定义了B到A的类型转换
    1struct A;
    2struct B
    3{
    4    operator A() const;
    5};
  3. 发生二义性
    1A f(const A &);             // 函数声明
    2B b;
    3A a = f(b);                // 错误:此时B转换为A类型时,有两种路径
    4
    5A a1 = f(b.opeartor A());  // 正确: 指定转换途径, 调用结果返回一个A类型的对象
    6A a2 = f(A(b));            // 正确: 指定转换途径
    

不要为类定义相同的类型转换


第二种情形: 多个向内置类型转换的类型转换函数 + 内置类型转换搭桥

  1. 类A
    1struct A
    2{
    3    A(int i = 0);
    4    A(double);
    5    operator int() const;
    6    operator double() const;
    7};
  2. 发生二义性
    1void f2(long double);
    2A a;
    3f2(a);         // 错误,可以是:A -> int -> long double ; A -> double -> long double
    4
    5long lg;
    6A a2(lg);      // 错误,可以是:long -> int ; long -> double
    7
    8short s = 42;
    9A a3(s);        // 匹配 A(int),因为short -> int 优于 short -> double
    

不要在类中定义两个及以上转换源或转换目标是算术类型的转换


第三种情形: 重载函数与转换构造函数

如果两个或多个类型转换都提供了同一种可行匹配, 这些类型转换一样好

 1struct C
 2{
 3    C(int);
 4};
 5
 6struct D
 7{
 8    D(int);
 9};
10void manip(const C&);
11void manip(const D&);
12
13manip(10);                // 错误,可以是: int -> C ; int -> D
14
15manip(C(10));             // 正确

如果在调用重载函数时, 需要使用构造函数或强制类型转换来改变实参的类型, 则这通常意味着程序的设计存在不足


第四种情形: 重载函数与用户定义的类型转换

调用重载函数时, 如果两个或多个用户定义的类型转换都提供了可行匹配, 我们认为这些类型转换一样好; 即使同时存在内置类型转换, 会对其进行忽略

 1struct C
 2{
 3    C(int);
 4};
 5struct E
 6{
 7    E(double);
 8};
 9void manip2(const C&);
10void manip2(const E&);
11
12manip2(10);        // 错误,可以是:先内置类型转换 int -> double,再 double -> E ; int -> C

即使int到C能精准匹配, 另一个需要int到double到E, 多一次内置类型转换, 编译器仍将该调用标示为错误


运算符重载: 类型转换运算符


用户定义的类型转换 user-defined conversions :

  1. 类的转换构造函数
  2. 重载类型转换运算符

转换构造函数和类型转换运算符共同定义了类类型转换 class-type conversions

必须定义为成员函数, 将本类型转换为目的类型


类型转换运算符

conversion operator

形如

1operator T() const;

将本类型转换为类型T, T不能为void

返回目的类型的值

和函数返回类型一样, T也不能是数组或函数, 可以是函数指针, 数组指针和引用

不能声明返回类型, 形参列表必须为空 : 源类型为自身, 目的类型在函数名中给出

类型转换运算符通常不应该改变待转换对象的内容, 一般被定义为const


示例

 1class SmallInt
 2{
 3public:
 4    SmallInt(int i = 0) : val(i)
 5    {
 6        if (i < 0 || i > 255)
 7            throw out_of_range("Bad SmallInt value");
 8    }
 9    operator int() const { return val; }
10private:
11    size_t val;
12};
13
14
15SmallInt si;
16si = 4;                // 使用转换构造函数将4转换SmallInt对象, 赋值给si
17si + 3;                // si被转换成int

编译器一次只能执行一个用户定义的类型转换

编译器一次只能执行一个用户定义的类型转换, 但隐式的类类型转换可以在内置转换之前或之后, 与其一起使用

1SmallInt si = 3.14;            // 只使用内置类型转换, 将double转换为int
2si + 3.14;                     // si转换成int(用户定义类型转换),int转换成double(内置类型转换)

SmallInt的转换构造函数接受所有算术类型

1SmallInt(int i = 0);

实际编写代码时, 较少为类定义类型转换运算符

一个常见的类型转换运算符需求: 目的类型为bool


显式的类型转换运算符

explicit conversion operator

  1. 可通过static_cast使用

     1class SmallInt
     2{
     3public:
     4    SmallInt(int i = 0) : val(i)
     5    {
     6        if (i < 0 || i > 255)
     7            throw out_of_range("Bad SmallInt value");
     8    }
     9    explicit operator int() const { return val; }
    10private:
    11    size_t val;
    12};
    13
    14SmallInt si = 3;           // 转换构造函数, 非显式
    15si + 3;                    // 错误: 类型转换运算符要求显式
    16static_cast<int>(si) + 3;  // 正确
    
  2. 如果表达式(变量)被用作条件,编译器会将显式的类型转换自动应用于它

    condition
    if、while和do while的条件部分
    for的条件表达式
    逻辑非 ! ,逻辑或,逻辑与的运算对象 &&
    条件运算符 ? :

将目的类型为bool的类型转换运算符声明为显式

  1. 移位操作

    如果cin向bool的类型转换非隐式, 编译器会将cin转换为bool, 并提升为int, 然后左移42位

    1int i = 42;
    2cin << i;
  2. IO类型提供向bool的显式类型转换

    • 输入运算符返回左操作数, 在while的条件部分发生向bool的显式类型转换
    • 如果cin的条件状态为good, 函数返回true; false
    1while (cin >> value)
    2{
    3    // 处理
    4}

避免有二义性的类型转换

确保类类型和目的类型之间只存在唯一一种转换方式


第一种情形: 多重转换路径

一个类定义了转换构造函数, 而另一个类重载了类型转换运算符

  1. 类A: 定义了B到A转换构造函数
    1struct B;
    2struct A
    3{
    4    A = default;
    5    A(const B &);
    6};
  2. 类B: 定义了B到A的类型转换
    1struct A;
    2struct B
    3{
    4    operator A() const;
    5};
  3. 发生二义性
    1A f(const A &);             // 函数声明
    2B b;
    3A a = f(b);                // 错误:此时B转换为A类型时,有两种路径
    4
    5A a1 = f(b.opeartor A());  // 正确: 指定转换途径, 调用结果返回一个A类型的对象
    6A a2 = f(A(b));            // 正确: 指定转换途径
    

不要为类定义相同的类型转换


第二种情形: 多个向内置类型转换的类型转换函数 + 内置类型转换搭桥

  1. 类A
    1struct A
    2{
    3    A(int i = 0);
    4    A(double);
    5    operator int() const;
    6    operator double() const;
    7};
  2. 发生二义性
    1void f2(long double);
    2A a;
    3f2(a);         // 错误,可以是:A -> int -> long double ; A -> double -> long double
    4
    5long lg;
    6A a2(lg);      // 错误,可以是:long -> int ; long -> double
    7
    8short s = 42;
    9A a3(s);        // 匹配 A(int),因为short -> int 优于 short -> double
    

不要在类中定义两个及以上转换源或转换目标是算术类型的转换


第三种情形: 重载函数与转换构造函数

如果两个或多个类型转换都提供了同一种可行匹配, 这些类型转换一样好

 1struct C
 2{
 3    C(int);
 4};
 5
 6struct D
 7{
 8    D(int);
 9};
10void manip(const C&);
11void manip(const D&);
12
13manip(10);                // 错误,可以是: int -> C ; int -> D
14
15manip(C(10));             // 正确

如果在调用重载函数时, 需要使用构造函数或强制类型转换来改变实参的类型, 则这通常意味着程序的设计存在不足


第四种情形: 重载函数与用户定义的类型转换

调用重载函数时, 如果两个或多个用户定义的类型转换都提供了可行匹配, 我们认为这些类型转换一样好; 即使同时存在内置类型转换, 会对其进行忽略

 1struct C
 2{
 3    C(int);
 4};
 5struct E
 6{
 7    E(double);
 8};
 9void manip2(const C&);
10void manip2(const E&);
11
12manip2(10);        // 错误,可以是:先内置类型转换 int -> double,再 double -> E ; int -> C

即使int到C能精准匹配, 另一个需要int到double到E, 多一次内置类型转换, 编译器仍将该调用标示为错误