如何确保显式构造函数被调用并允许大括号初始化?
How can I ensure explicit constructors are called and allow brace initialization?
考虑下面的基本程序。它有 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
在构造函数上阻止使用构造函数,例如当将某些内容传递给期望您的 class 类型作为参数的函数时,作为隐式用户定义的转换。正如@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!");
}
std::is_same_v
和 C++17 需要 #include<type_traits>
。 (在 C++17 之前你需要使用 std::is_same<...>::value
。)
或者,如果您不希望使用错误的类型导致硬错误,您可以使用 std::enable_if
而不是 static_assert
。
考虑下面的基本程序。它有 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
在构造函数上阻止使用构造函数,例如当将某些内容传递给期望您的 class 类型作为参数的函数时,作为隐式用户定义的转换。正如@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!");
}
std::is_same_v
和 C++17 需要 #include<type_traits>
。 (在 C++17 之前你需要使用 std::is_same<...>::value
。)
或者,如果您不希望使用错误的类型导致硬错误,您可以使用 std::enable_if
而不是 static_assert
。