Скажем, класс Foo имеет две зависимости (Bar и Baz) и что это ошибка для создания Foo без предоставления обоих из них. Инъекция конструктора позволяет во время компиляции гарантировать, что это будет сделано:
class Foo
{
public:
Foo(const std::shared_ptr<Bar>& bar, const std::shared_ptr<Baz>& baz);
// (don't get hung up on the type of pointer used; it for example only)
};
Но позвольте сказать, что Foo также нуждается в двух двухлокальных номерах:
class Foo
{
public:
Foo(const std::shared_ptr<Bar>& bar, const std::shared_ptr<Baz>& baz,
double val1, double val2);
};
Теперь есть проблема; было бы очень просто, чтобы вызывающий случайно переместил val1 и val2 и создал ошибку времени выполнения. Мы можем добавить структуру Params, чтобы разрешить именованную инициализацию и исключить это:
class Foo
{
public:
struct Params
{
std::shared_ptr<Bar> bar;
std::shared_ptr<Baz> baz;
double val1;
double val2
};
Foo(const Params& params);
};
// ...
std::shared_ptr<Foo> MakeDefaultFoo()
{
Foo::Params p;
p.bar = std::make_shared<Bar>();
p.baz = std::make_shared<Baz>();
p.val1 = 4.0;
p.val2 = 3.0;
return std::make_shared<Foo>(p);
}
Но теперь у нас есть проблема, что вызывающий может забыть заполнить одно из полей в Params, которое не будет обнаруживаться до выполнения. синтаксис инициализации структуры или список инициализаторов сделали бы невозможным забыть поле, но затем мы вернемся к позиции!
Есть ли какой-то трюк, который позволяет иметь лучшее из обоих миров - обязательные аргументы, связанные с компилятором, которые назначаются именем вместо позиции?
Просто иметь простой обертку может работать:
template <typename Tag, typename T>
struct Argument {
explicit Argument( const T &val );
T get() const;
};
class Foo {
public:
struct Val1Tag;
struct Val2Tag;
typedef Argument<Val1Tag,double> Val1;
typedef Argument<Val2Tag,double> Val2;
Foo( Val1 v1, Val2 v2 );
};
Foo foo( Foo::Val1( 1.0 ), Foo::Val2( 2.3 ) );
Теперь типы являются явными, и вы не можете их обменивать без получения ошибки компилятора.
Val1
и Val2
относятся к Val1
и Val2
же типу, поэтому они взаимозаменяемы.
Очень любопытно узнать, что делает cdhowie, но в то же время простая оболочка с разными типами может решить некоторые проблемы:
struct Val1 {
explicit Val1(double v) : v(v) { }
operator double() const { return v; }
double v;
};
// copy for Val2
class Foo
{
public:
Foo(const std::shared_ptr<Bar>& bar, const std::shared_ptr<Baz>& baz,
Val1 val1, Val2 val2);
};
Таким образом, вы не можете их смешивать, так как вам нужно построить Foo:
Foo foo(bar, baz, Val1{3.0}, Val2{7.0});
Это куча лишней типизации, чтобы убедиться, что типы разные, и вам обязательно нужно убедиться, что конструктор explicit
(или он побеждает точку), но это помогает.
template <typename T> class Wrapper ...
то вы можете просто унаследовать это и ввести базовый конструктор с using
. Тогда Val1
и Val2
могут быть почти однострочными.
Что-то вроде этого (untested)
template <typename tag, typename t>
struct param
{
explicit param(t vv)
: v(vv) {}
param(const param& p)
: v(p.v) {}
t v;
};
struct one{}; struct two {};
using paramone = param<one, double>;
using paramtwo = param<two, double>;
void somefunc (paramone p1, paramtwo p2)
{ ... };
void somefunc (paramtwo p2, paramone p1)
{ somefunc(p1, p2); }
// using it
somefunc (2, 3); // bad
somefunc (paramone(2), paramtwo(3)); // good
somefunc (paramtwo(3), paramone(2)); // also good
Params() : val1(0), val2(0) { }
чтобы, если значение не установлено, по крайней мере, было какое-то вменяемое значение по умолчанию.