考虑下面的基本程序。它typedef int Number
,目的是允许未来的开发人员将其更改为当时最有意义的精度(甚至使用CGAL的数字类型之一(。
你可以看到一个Point
被描述为一对Number
,而一个LineSegment
被描述为两个Point
。
我想实现两个目标:
- 如果用户尝试使用显式
Number
以外的任何内容实例化Point
,则应触发编译时警告。我认为这就是explicit
关键字的作用。相反,正如您在p3
的实例化中看到的那样,我们静默地允许将float
转换为int
- 如果用户想要用大括号初始化具有常量值的
LineSegment
,只要这些值Number
就可以。
我的问题:
- 我是否误解了应该如何使用
explicit
? - 期望编译器捕获此内容是不可行的吗?换句话说,它是否需要在运行时的编程逻辑中?
typedef int Number;
class Point
{
public:
explicit Point(Number n1, Number n2)
: x(n1), y(n2)
{}
Number x;
Number y;
};
class LineSegment
{
public:
LineSegment(Point p1, Point p2)
: start(p1), end(p2)
{}
Point start;
Point end;
};
int main()
{
// Works as expected
Point p1(10, 10);
Point p2(20, 20);
LineSegment s1(p1, p2);
// How can I trigger a compiler error here?
Point p3(10.5, 11.5);
// Why can't a do the following?
//LineSegment s2({30, 30}, {40, 40});
return 0;
}
是的,你误解了explicit
. 构造函数上的explicit
阻止构造函数被用作隐式用户定义转换,例如,当某些内容传递给期望您的类类型作为参数的函数时。正如@JohannesSchaub在问题注释中指出的那样,在构造函数上使用explicit
实际上是阻止LineSegment s2({30, 30}, {40, 40});
出于这个确切原因进行编译的原因。
explicit
不会阻止构造函数自己的参数中的隐式转换。
使用括号中的初始值设定项语法时无法避免隐式转换,但可以通过使构造函数成为模板函数并检查其模板参数类型来获取传递的实际类型:
template<typename T1, typename T2>
Point(T1 n1, T2 n2)
: x(n1), y(n2)
{
static_assert(std::is_same_v<T1,Number> && std::is_same_v<T2,Number>
, "Construct Point with Number arguments!");
}
需要#include<type_traits>
才能std::is_same_v
和C++17。(在 C++17 之前,您需要使用std::is_same<...>::value
。
或者,如果您不希望使用错误的类型导致硬错误,则可以使用std::enable_if
而不是static_assert
。