【C++11】显式转换操作符

隐式类型转换是C++的一个既好又坏的特性。它给人以方便,但可能造成一些十分隐晦的错误。

类型转换提供了一个类型向另一个类型的构造。

class X
{
public:
    operator int() const noexcept
    {
        return 42;
    }
};

void Func(int) {}

int wmain()
{
    X x0;
    X x1;
    Func(x0);
    Func(x1);

    int n = x0 + x1;
    std::cout << n << std::endl; // 84

    return 0;
}

上面的代码中,X可以隐式地转换为int,于是函数Func可以接受X类型的参数,x0与x1也可以用+来做运算。

在实际的编程工作中,一个更常见的例子是,我们自己定义的字符串类(记为String)重载了operator const wchar_t*():

class String
{
public:
    operator const wchar_t*() const noexcept
    {
        // 函数体
    }
};

从而,如果一个函数需要const wchar_t*类型的参数,就可以直接传入一个String实例。

但是,重载类型转换也不是万无一失的。比如,重载operator bool()。

重载operator bool()所带来的问题比较多,以至于很多企业的编码规范中,都不提倡甚至禁止重载operator bool()。

由于bool属于算数类型,所以重载了operator bool()的类的实例可以被用在任何需要算术类型的上下文中。

class Y
{
private:
    int m_;
public:
    explicit Y(int m) :m_{ m } {}
    operator bool() const noexcept
    {
        return (m_ != 0);
    }
};

int wmain()
{
    Y y0{ 12 };
    Y y1{ 25 };
    auto n = y0 + y1; // !!!
    std::cout << n << std::endl;
    return 0;
}

毫无意义的y0 + y1竟然(无警告地)编译通过,而且还通过+产生了一个int,这实在不合理。可能程序作者想要的是Y(38),更可能的是后来维护代码的人根本无法知道原作者想干什么。随着代码的规模变大,这些细微的隐患会越埋越深,或许,将来花费两天时间找到的BUG就是由它引起的。

为了防止这样的异常情况,C++11引入了显式的类型转换运算符

class X
{
public:
    explicit operator int() const noexcept
    {
        return 42;
    }
};

void Func(int) {}

int wmain()
{
    X x0;
    Func(x0); // 错误,不存在从 X 到 int 的(隐式)转换
    int y = x0; // 错误,不存在从 X 到 int 的(隐式)转换
    Func((int)x0); // 正确1
    Func(int(x0)); // 正确2
    Func(static_cast<int>(x0)); // 正确3

    return 0;
}

用explicit修饰的类型转换运算符,则相应的类型转换必须显式地进行。C式(正确1),函数式(正确2),static_cast(正确3)都行。

但是,显式的类型转换有一个例外。如果表达式被用作条件,那么显式的类型转换也可以隐式地进行。“被用作条件”即:

  • if、while及do语句的条件部分;
  • for语句头的条件表达式;
  • 逻辑非运算符(!)、逻辑或运算符(||)、逻辑与运算符(&&)的运算对象;
  • 条件运算符(x ? y : z)的条件表达式。

由于转换到bool一般被用作条件,所以operator bool()一般用explicit来修饰。

class K
{
public:
    explicit operator bool() const noexcept
    {
        return false;
    }
};

int wmain()
{
    K k0;
    if (k0) // 正确
    {
        std::cout << "qwer" << std::endl;
    }
    else
    {
        std::cout << "zxcv" << std::endl;
    }
    return 0;
}
时间: 2024-11-05 17:23:58

【C++11】显式转换操作符的相关文章

C++11显式转换操作符

C++11之前,已经支持显式转换操作符 #include <iostream> using namespace std; template <typename T> class Ptr { public: Ptr(T* p): _p(p) {} operator bool() const { if (_p != 0) return true; else return false; } private: T* _p; }; int main() { int a; Ptr<int

C++11 显式转换操作符

[1]显式转换操作符 以前对explicit关键字的理解可以参考随笔<explicit关键字>. 而在C++11中,标准将explicit的使用范围扩展到了自定义的类型转换操作符上,以支持所谓的“显式类型转换”. explicit关键字作用于类型转换操作符上,意味着只有在直接构造目标类型 或 显式类型转换的时候可以使用该类型. 应用示例: 1 #include <iostream> 2 using namespace std; 3 4 class ConvertTo 5 { 6 }

C++11 FAQ中文版--转

更新至英文版October 3, 2012 译者前言: 经过C++标准委员会的不懈努力,最新的ISO C++标准C++11,也即是原来的C++0x,已经正式发布了.让我们欢迎C++11! 今天获得Stroustrup先生的许可,开始翻译由他撰写和维护的C++11 FAQ.我 觉得这是一件伟大而光荣的事情,但是我又觉得压力很大,因为我的英语水平很差劲,同时自己的C++水平也很有限,很害怕在翻译过程中出现什么错误,贻笑大方不要紧,而误人子弟就罪过大了.所以,我这里的翻译只能算是抛砖引玉,如果你的英文

Java编程思想笔记(操作符)

1.更简单的打印语句:print();     2.使用Java操作符:1.例外的操作符“=”.“==”.“!=”,这些操作符能操作所有的对象.2.String支持+=.+     3.优先级     4.赋值:直接操作对象内的域容易导致混乱     4(1).方法调用中的别名问题     5.算数操作符:Random rand = new Random(47);//随机生成器对于特定的种子值总是产生相同的随机数序列     5(1).一元加.减操作符     6.自动递增和递减:++a(先加,

二、操作符

1.赋值 别名现象: 1)对象别名:对象a,b a = b; a的原引用丢失,被垃圾回收,a的新引用指向b的对象内容: 避免方式: a.name = b.name; 彼此对象独立: 2)方法别名: f(Letter y){y.c = '2';} class Letter{char c;} { Letter x = new Letter(); x.c = '1'; System.out.print(x.c); f(x); System.out.print(x.c); } 结果:1 2 2.算数操作

[Thinking in Java]第3章-操作符

3.1 更简单的打印语句 3.2 使用Java操作符 3.3 优先级 3.4 赋值 3.5 算术操作符 3.6 自动递增和递减 3.7 关系操作符 3.8 逻辑操作符 3.9 直接常量 3.10 按位操作符 3.11 移位操作符 3.12 条件操作符 3.13 字符串操作符+和+= 3.14 类型转换 目录 3.1 更简单的打印语句 学习编程语言的同学遇到的第一个程序无非是打印Hello, world了,然而在Java中要写成 System.out.println("Hello, world&q

变量、操作符、表达式

变量.操作符.表达式 1.语句是能执行一个操作的命令.最简单也是最重要的一个C#语法规则:必须用一个分号来终止所有语句. 2.标识符遵循的规则: ▲只能使用字母.数字.下划线字符 ▲标识符必须以一个字母或者下划线开头 3.关键字及其含义 关键字 含义 abstract 标识一个可以扩展但不能被实体化的,必须被实现的类或方法. as 一个转换操作符,如果转换失败,就返回null. base 用于访问被派生类或构造中的同名成员隐藏的基类成员. bool 表示布尔值的简单类型. break 用于从lo

C++11的for循环,以及范围Range类的实现

C++11支持range-based for循环.这是一个很方便的特性,能省挺多代码.以下代码就能很方便的遍历vector中的元素,并打印出来: 1 2 3 4 5 6 7 8 std::vector<int> int_vec; int_vec.push_back(1); int_vec.push_back(2); //如果要修改int_vec中的元素,将变量x声明为 int& 即可 for (int x: int_vec) {     std::cout << x <

操作符 Thinking in Java 第三章

3.1 更简单的打印语句 3.2 使用Java操作符 3.3 优先级 *int类型+String类型  直接转换为String类型 3.4 赋值 1. *引用=引用  两个引用指向同一个对象,所以操作任何一个引用都会对对象传递消息,执行操作: 2. 直接操作对象内的域容易导致混乱,且违背了良好面向对象的设计原则: * get()和set()方法的出现,解决此问题: 3.5 算数操作符 1. Class Random 产生随机数的类 2. Random 使用: Class Random rand