支持两个具有不同语义的整数构造函数

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";
}

Live demo.

现在让它能够处理奇怪的单位

现在,我想要一种简单的方法让用户用磅(或石头,或其他任何东西)构造 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 }
};

Live demo.

这不起作用,因为 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";
}

Live demo.

改进

现在您已经命名了语义,您可以更进一步并提供丰富的重载集:

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; }
};

Live demo.

这里需要注意的是逻辑(单位换算)还在Mass的实现中; poundsstone只是名称:语义。在这种情况下,这可能无关紧要(一公斤会在很长一段时间内保持约 0.16 stones),但通常您应该更愿意将这些实现细节封装在同一个地方。