支持两个具有不同语义的整数构造函数
Supporting two integer constructors with different semantics
我正在设计一个 class,它有两个带有单个整数参数的构造函数,这些参数具有独立的语义。
template <typename value_t>
class Numeric {
value_t value_;
public:
Numeric(value_t value) : value_(value) {}
Numeric(long l) { /* computes value_ from l */ }
Numeric(double d) { /* computes value_ from d */ }
Numeric(std::string bstring) { /* computes value_ from binary string */ }
// ...
};
value_t
必须 在这种情况下是一个整数类型,它甚至可以是 long
的别名(在这种情况下这甚至不会编译)。即使它不是 long
的类型别名,我也不确定整数提升如何混淆两个构造函数。
我的想法是,我想通过提供基础表示来支持用户构建,或通过提供任何可能的数字表示,然后将其转换为基础表示(A.K.A value_t
).
隔离构造函数的最佳方法是什么?
强烈建议您重新考虑设计。为什么需要两个看起来一样的构造函数,但做两件不同的事情?
无论如何,你可以使用标签:
struct tag_a{};
struct tag_b{};
template <typename value_t>
class Example {
value_t value_;
public:
Example(value_t value, tag_a) : value_(value) {}
Example(long l, tag_b) { }
};
用法
long x = 123;
auto a = Example<long>(x,tag_a());
auto b = Example<long>(x,tag_b());
我建议制作构造函数 private
并使用静态函数代替 "create" 您的实例。静态函数可以有有意义的名称,以便明确告诉用户对每个函数的期望:
template <typename value_t>
class Example {
value_t value_;
//Prevent users to directly create instances
Example(value_t value): value_(value)
{
}
public:
static Example createFromValue(value_t value)
{
return Example(value);
}
static Example createComputingValueFromLong(long l)
{
return Example(/*Compute from l*/l);
}
};
即使你仍然想使用那些构造函数而不是分配内部变量,你可以将它们放在私有部分并从 static
函数中调用它们,这样用户将永远无法直接打电话给他们。
I am designing a class that has two constructors with single integer arguments that have separate semantics.
What's the best way to segregate the constructors?
您的代码应该反映您的语义。
当有人看到您的class界面时,他们必须立即猜出它的正确用法是什么。由于我不知道你的用例背后的语义,我会自己来说明。
一个简单的class来处理质量
设想以下 class 来处理群众;我们将增强它以提供额外的可构造性:
template<class T>
class Mass
{
T _kg;
public:
Mass(T kg) : _kg(kg) {}
T kg() const { return _kg; }
};
用法:
#include <iostream>
int main()
{
Mass<double> one_litter_of_water(1.0);
std::cout << "1 L of water is: " << one_litter_of_water.kg() << " kg\n";
}
现在让它能够处理奇怪的单位
现在,我想要一种简单的方法让用户用磅(或石头,或其他任何东西)构造 Mass
:
template<class T>
class Mass
{
T _kg;
public:
Mass(T kg) : _kg(kg) {}
Mass(double lb) : _kg(lb/2.2046) {} // ho no!
T kg() const { return _kg }
};
这不起作用,因为 T
可以是 double
。
使语义明显
解决方案就像命名一样简单。对于采用相同类型的多个参数的函数尤其如此。想象一下,有一天您阅读了一些晦涩的旧代码并且偶然发现:
draw(2.6, 2.8, 54.1, 26.0); // draw selection
它有什么作用?好吧,显然它画了一些东西。它有四个double参数,它可能是一个矩形。你花一些时间,去看看 draw
的声明,找到它的文档,...并弄清楚它在给定两个点的情况下绘制了一个矩形。可以是一个点,一个宽度和一个高度,也可以是很多东西。
在另一种生活中,想象一下你发现的不是上面的线:
draw(Point{2.6, 2.8}, Point{54.1, 26.0}); // draw selection
现在是不是显而易见?
在我们的大规模案例中使语义明显
struct pounds { double value; operator double() const { return value; } };
template<class T>
class Mass
{
T _kg;
public:
Mass(T kg) : _kg(kg) {}
Mass(pounds lb) : _kg(lb/2.2046) {}
T kg() const { return _kg; }
};
用户可以显然这样使用它:
#include <iostream>
int main()
{
Mass<double> one_litter_of_water(pounds{2.2046});
std::cout << "1 L of water is: " << one_litter_of_water.kg() << " kg\n";
}
改进
现在您已经命名了语义,您可以更进一步并提供丰富的重载集:
struct unit { double value; operator double() const { return value; } };
struct pounds : unit {};
struct stones : unit {};
struct grams : unit {};
template<class T>
class Mass
{
T _kg;
public:
Mass(T kg) : _kg(kg) {}
Mass(pounds lb) : _kg(lb/2.2046) {}
Mass(stones st) : _kg(st/0.1575) {}
Mass(grams g) : _kg(g/1000.0) {}
T kg() const { return _kg; }
};
这里需要注意的是逻辑(单位换算)还在Mass
的实现中; pounds
、stone
、等只是名称:语义。在这种情况下,这可能无关紧要(一公斤会在很长一段时间内保持约 0.16 stones),但通常您应该更愿意将这些实现细节封装在同一个地方。
我正在设计一个 class,它有两个带有单个整数参数的构造函数,这些参数具有独立的语义。
template <typename value_t>
class Numeric {
value_t value_;
public:
Numeric(value_t value) : value_(value) {}
Numeric(long l) { /* computes value_ from l */ }
Numeric(double d) { /* computes value_ from d */ }
Numeric(std::string bstring) { /* computes value_ from binary string */ }
// ...
};
value_t
必须 在这种情况下是一个整数类型,它甚至可以是 long
的别名(在这种情况下这甚至不会编译)。即使它不是 long
的类型别名,我也不确定整数提升如何混淆两个构造函数。
我的想法是,我想通过提供基础表示来支持用户构建,或通过提供任何可能的数字表示,然后将其转换为基础表示(A.K.A value_t
).
隔离构造函数的最佳方法是什么?
强烈建议您重新考虑设计。为什么需要两个看起来一样的构造函数,但做两件不同的事情?
无论如何,你可以使用标签:
struct tag_a{};
struct tag_b{};
template <typename value_t>
class Example {
value_t value_;
public:
Example(value_t value, tag_a) : value_(value) {}
Example(long l, tag_b) { }
};
用法
long x = 123;
auto a = Example<long>(x,tag_a());
auto b = Example<long>(x,tag_b());
我建议制作构造函数 private
并使用静态函数代替 "create" 您的实例。静态函数可以有有意义的名称,以便明确告诉用户对每个函数的期望:
template <typename value_t>
class Example {
value_t value_;
//Prevent users to directly create instances
Example(value_t value): value_(value)
{
}
public:
static Example createFromValue(value_t value)
{
return Example(value);
}
static Example createComputingValueFromLong(long l)
{
return Example(/*Compute from l*/l);
}
};
即使你仍然想使用那些构造函数而不是分配内部变量,你可以将它们放在私有部分并从 static
函数中调用它们,这样用户将永远无法直接打电话给他们。
I am designing a class that has two constructors with single integer arguments that have separate semantics.
What's the best way to segregate the constructors?
您的代码应该反映您的语义。
当有人看到您的class界面时,他们必须立即猜出它的正确用法是什么。由于我不知道你的用例背后的语义,我会自己来说明。
一个简单的class来处理质量
设想以下 class 来处理群众;我们将增强它以提供额外的可构造性:
template<class T>
class Mass
{
T _kg;
public:
Mass(T kg) : _kg(kg) {}
T kg() const { return _kg; }
};
用法:
#include <iostream>
int main()
{
Mass<double> one_litter_of_water(1.0);
std::cout << "1 L of water is: " << one_litter_of_water.kg() << " kg\n";
}
现在让它能够处理奇怪的单位
现在,我想要一种简单的方法让用户用磅(或石头,或其他任何东西)构造 Mass
:
template<class T>
class Mass
{
T _kg;
public:
Mass(T kg) : _kg(kg) {}
Mass(double lb) : _kg(lb/2.2046) {} // ho no!
T kg() const { return _kg }
};
这不起作用,因为 T
可以是 double
。
使语义明显
解决方案就像命名一样简单。对于采用相同类型的多个参数的函数尤其如此。想象一下,有一天您阅读了一些晦涩的旧代码并且偶然发现:
draw(2.6, 2.8, 54.1, 26.0); // draw selection
它有什么作用?好吧,显然它画了一些东西。它有四个double参数,它可能是一个矩形。你花一些时间,去看看 draw
的声明,找到它的文档,...并弄清楚它在给定两个点的情况下绘制了一个矩形。可以是一个点,一个宽度和一个高度,也可以是很多东西。
在另一种生活中,想象一下你发现的不是上面的线:
draw(Point{2.6, 2.8}, Point{54.1, 26.0}); // draw selection
现在是不是显而易见?
在我们的大规模案例中使语义明显
struct pounds { double value; operator double() const { return value; } };
template<class T>
class Mass
{
T _kg;
public:
Mass(T kg) : _kg(kg) {}
Mass(pounds lb) : _kg(lb/2.2046) {}
T kg() const { return _kg; }
};
用户可以显然这样使用它:
#include <iostream>
int main()
{
Mass<double> one_litter_of_water(pounds{2.2046});
std::cout << "1 L of water is: " << one_litter_of_water.kg() << " kg\n";
}
改进
现在您已经命名了语义,您可以更进一步并提供丰富的重载集:
struct unit { double value; operator double() const { return value; } };
struct pounds : unit {};
struct stones : unit {};
struct grams : unit {};
template<class T>
class Mass
{
T _kg;
public:
Mass(T kg) : _kg(kg) {}
Mass(pounds lb) : _kg(lb/2.2046) {}
Mass(stones st) : _kg(st/0.1575) {}
Mass(grams g) : _kg(g/1000.0) {}
T kg() const { return _kg; }
};
这里需要注意的是逻辑(单位换算)还在Mass
的实现中; pounds
、stone
、等只是名称:语义。在这种情况下,这可能无关紧要(一公斤会在很长一段时间内保持约 0.16 stones),但通常您应该更愿意将这些实现细节封装在同一个地方。