浅析 explicit 关键字

本文详细介绍了C++中的explicit关键字,如何限制隐式转换和复制初始化,以增强代码的可读性和避免潜在错误。通过实例分析,展示了explicit关键字在构造函数和转换函数上的应用及其作用。
摘要由CSDN通过智能技术生成

浅析 explicit 关键字

前言

​ C++ 提供了多种方式来实现类型转换和构造对象,然而,有时候这些方式会导致一些意想不到的结果,比如隐式转换复制初始化。为了避免这些潜在的问题,C++ 引入了 explicit 关键字,它可以限制一些不必要或不安全的转换和初始化。在本文中,我们将介绍 explicit 关键字的作用和用法,以及它如何提高代码的可读性和安全性。


案例剖析

​ 为了说明 explicit 关键字的作用和用法,我们首先定义两个结构体 A 和 B,它们都有一个接受 int 参数的构造函数和一个返回 bool 值的转换函数,但是 B 的构造函数和转换函数都被 explicit 修饰了,而 A 的没有。

struct A 
{
	A(int) {}
	operator bool() const { return true; }
};

struct B 
{
	explicit B(int) {}
	explicit operator bool() const { return true; }
};

接下来,我们定义两个函数 doA 和 doB,它们分别接受 A 和 B 类型的参数。

void doA(A a) {}

void doB(B b) {}

然后,我们在 main 函数中创建一些 A 和 B 类型的对象,并尝试用不同的方式进行初始化和转换。

int main() {
  A a1(1);     // OK:直接初始化
  A a2 = 1;    // OK:复制初始化
  A a3{1};     // OK:直接列表初始化
  A a4 = {1};  // OK:复制列表初始化
  A a5 = (A)1; // OK:允许 static_cast 的显式转换
  doA(1);      // OK:允许从 int 到 A 的隐式转换
  if (a1)
    ; // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
  bool a6(a1); // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
  bool a7 = a1; // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
  bool a8 = static_cast<bool>(a1); // OK :static_cast 进行直接初始化

  B b1(1);     // OK:直接初始化
               //    B b2 = 1;        // 错误:被 explicit
               //    修饰构造函数的对象不可以复制初始化
  B b3{1};     // OK:直接列表初始化
               //    B b4 = { 1 };        // 错误:被 explicit
               //    修饰构造函数的对象不可以复制列表初始化
  B b5 = (B)1; // OK:允许 static_cast 的显式转换
               //    doB(1);            // 错误:被 explicit
               //    修饰构造函数的对象不可以从 int 到 B 的隐式转换
  if (b1)
    ; // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool
      // 的按语境转换
  bool b6(b1); // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B
               // 到 bool 的按语境转换
               //    bool b7 = b1;        // 错误:被 explicit 修饰转换函数
               //    B::operator bool() 的对象不可以隐式转换
  bool b8 = static_cast<bool>(b1); // OK:static_cast 进行直接初始化

  return 0;
}

从上面的代码中,我们可以看出以下几点:

  • explicit 修饰的构造函数只能用于直接初始化直接列表初始化不能用于复制初始化和复制列表初始化这样可以避免一些不必要的类型转换,比如将一个 int 值赋给一个 B 类型的变量,或者将一个花括号列表赋给一个 B 类型的变量。
  • explicit 修饰的转换函数只能用于显式转换和按语境转换,不能用于隐式转换。这样可以避免一些不安全的类型转换,比如将一个 B 类型的对象赋给一个 bool 类型的变量,或者将一个 B 类型的对象作为条件表达式。按语境转换是指在某些特定的语境中,编译器会自动调用 explicit 修饰的转换函数,比如 if 语句,while 语句,for 语句,switch 语句,逻辑运算符,条件运算符,static_cast,和 bool 构造函数。
  • explicit 关键字可以提高代码的清晰度和一致性,避免一些潜在的错误和歧义。

补充案例

上面案例中A、B构造函数都属于单参数类型,下面印证仅有多参数构造函数的类是否存在同样的结论。此处以“分数”为例构建结构体案例:

例如,我们可以定义一个表示分数的结构体 Fraction,它有一个接受两个 int 参数的构造函数,表示分子和分母,以及一个返回 double 值的转换函数,表示分数的小数值。

我们可以用 explicit 关键字来修饰这两个函数,以防止一些不合理或不明确的转换和初始化

struct Fraction 
{
	int num; // 分子
	int den; // 分母

	// explicit 修饰的构造函数,防止从 int 或 int,int 到 Fraction 的隐式转换和复制初始化
	explicit Fraction(int n, int d = 1) : num(n), den(d) {}

	// explicit 修饰的转换函数,防止从 Fraction 到 double 的隐式转换
	explicit operator double() const { return static_cast<double>(num) / den; }
};

接着创建一些 Fraction 类型的对象,并尝试用不同的方式进行初始化和转换。

Fraction f1(1, 2); // OK:直接初始化
//		Fraction f2 = 1;        // 错误:被 explicit 修饰的构造函数不可以复制初始化
//		Fraction f3 = 1, 2;     // 错误:被 explicit 修饰的构造函数不可以复制初始化
Fraction f4{ 1, 2 }; // OK:直接列表初始化
//		Fraction f5 = { 1, 2 };	// 错误:被 explicit 修饰的构造函数不可以复制列表初始化
Fraction f6 = (Fraction)1;	// OK:允许 static_cast 的显式转换
Fraction f7 = static_cast<Fraction>(1); // OK:static_cast 进行直接初始化

//		double d1 = f1; // 错误:被 explicit 修饰的转换函数不可以隐式转换
double d2(f1);  // OK:被 explicit 修饰的转换函数可以按语境转换
double d3 = static_cast<double>(f1); // OK:static_cast 进行直接初始化

我们看到上面 f6、f7 对象通过类型强转生成 Fraction 对象这个过程属于显式转换,所以经过强转生成的临时对象可以成功执行拷贝构造和赋值运算函数将值传给 f6、f7 对象。

  • explicit 修饰的构造函数可以防止从 int 或 int,int 到 Fraction 的隐式转换和复制初始化。例如,如果我们想要创建一个表示 1/2 的分数,我们应该使用 Fraction f(1, 2) 或 Fraction f{1, 2},而不是 Fraction f = 1 或 Fraction f = {1, 2},因为后者会创建一个表示 1/1 或 2/1 的分数,这显然不是我们的目的。
  • explicit 修饰的转换函数可以防止从 Fraction 到 double 的隐式转换。例如,如果我们想要计算一个分数的倒数,我们应该使用 static_cast<double>(f)来显式地将分数转换为 double 类型,然后再进行除法运算,而不是直接使用 1 / f,因为后者会先将 1 隐式地转换为 Fraction 类型,然后再调用 Fraction 类的重载的除法运算符,这可能会得到一个错误的结果。

总结

  • explicit 修饰构造函数时,可以防止隐式转换和复制初始化
  • explicit 修饰转换函数时,可以防止隐式转换,但按语境转换除外

​ 在本文中介绍了 explicit 关键字的作用和用法,以及它如何提高代码的可读性和安全性。我们了解了 explicit 关键字可以用于修饰构造函数和转换函数,以阻止隐式转换和复制初始化,也了解了 explicit 修饰的构造函数和转换函数的使用场景和限制。我们认识到 explicit 关键字可以帮助我们避免一些不必要或不安全的类型转换,提高代码的清晰度和一致性。希望本文对你有所帮助,如果你有任何问题或建议,欢迎在评论区留言。谢谢!

在这里插入图片描述

  • 18
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

螺蛳粉只吃炸蛋的走风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值