更好地理解命名构造函数的习惯用法
我想让一个角度类以弧度或度数初始化,我想返回值而不是角度对象。我发现命名构造函数可能是最有效的方法,但我不能 100% 确定我将如何修改我的案例。
#pragma once
#define _USE_MATH_DEFINES
#include <cmath>
class Angle
{
public:
static Angle toRadians(double value)
{
return Angle((value * M_PI / 180.0f));
}
static Angle toDegrees(double value)
{
return Angle(value / 180.0f * M_PI);
}
private:
double angle;
Angle(double value) : angle(value) {};
};
std::cout << Angle::toRadians(19.48); // Should print 0.33999014
回答
你的Angle班级缺少一个不变式。也就是说,对于Angle类的任意对象,除了“它包含一个双精度”之外,没有什么可以说是真的。
考虑一下:如果我编写以下函数:
void do_something(Angle delta) {
// do something
}
如果它可以包含度数或弧度,我应该如何使用 delta 并且我无法知道它是哪个?
信不信由你,这是您与构造函数斗争的根源。这是因为构造函数的主要工作是将对象置于其不变状态。没有不变量意味着没有参考点,所以不清楚构造函数应该做什么。
因此,让我们设置一个让我们do_something()自信地实现的不变量:“角度始终包含弧度”。您甚至可以使用“...并且该角度始终在[-pi, pi[范围内”来使用更严格的不变量。
很明显,您的toRadians()和toDegrees()命名的构造函数在这种情况下毫无意义。事实上,他们从来没有真正做过,但现在很明显。我们想要的是从它们的参数中建立不变量的函数。fromRadians()并且fromDegrees()会更有意义。
class Angle {
public:
static Angle fromRadians(double value)
{
return Angle(value);
}
static Angle fromDegrees(double value)
{
return Angle(value / 180.0 * M_PI);
}
double asRadians() const {
return radians;
}
double asDegrees() const {
return radians / M_PI * 180.0;
}
private:
Angle(double value) : radians(value) {}
double radians;
};
请注意,使用这样实现的类,使用Angle该类的代码甚至不必知道不变量。API 只是说:角度可以由度数或弧度构成,并且可以解释为度数或弧度。您将不变量设置为在实现类时指导您的标志。一旦构建了类,它们就不再有用了,直到您当然想要更新代码。
接下来,如果您希望能够轻松地将角度转储为人类可读的字符串,您可以实现以下operator<<(sts::ostream&, const Angle&)功能:
std::ostream& operator<<(std::ostream& stream, const Angle& angle) {
stream << angle.asDegrees() << "deg";
return stream;
}