Я разрабатываю класс, который имеет два конструктора с однозначными целочисленными аргументами, которые имеют отдельную семантику.
Каков наилучший способ разделения конструкторов?
Ваш код должен отражать вашу семантику.
Когда кто-то видит интерфейс вашего класса, он должен немедленно угадать, каково его правильное использование.Поскольку я не могу понять семантику, лежащую в основе вашего варианта использования, я сделаю свой собственный, чтобы проиллюстрировать это.
Простой класс для обработки масс
Представьте себе следующий класс для обработки масс;мы расширим его, чтобы обеспечить дополнительную конструктивность:
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 демо .
А теперь сделайте так, чтобы он могобрабатывать странные единицы
Теперь я бы хотел, чтобы пользователь мог просто построить 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
Что это делает?Ну, очевидно, это что-то привлекает.Требуется четыре двойных параметра, это может быть прямоугольник.Вы потратили некоторое время, посмотрите объявление 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; }
};
Live demo .
Важно отметить, что логика (преобразование единиц) все еще находится в реализации Mass
;pounds
, stone
, и т. Д. - это просто названия: семантика.В этом контексте это может не иметь значения (один килограмм останется ~ 0,16 камня в течение длительного времени), но в целом вы должны предпочесть инкапсулировать эти детали реализации в одном месте.