Сделайте аргумент обязательным, не полагаясь на позицию

0

Скажем, класс 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, которое не будет обнаруживаться до выполнения. синтаксис инициализации структуры или список инициализаторов сделали бы невозможным забыть поле, но затем мы вернемся к позиции!

Есть ли какой-то трюк, который позволяет иметь лучшее из обоих миров - обязательные аргументы, связанные с компилятором, которые назначаются именем вместо позиции?

  • 0
    Вы можете добавить Params() : val1(0), val2(0) { } чтобы, если значение не установлено, по крайней мере, было какое-то вменяемое значение по умолчанию.
  • 0
    @cdhowie Когда я вынужден прибегнуть к тому , что я на самом деле предпочитаю в здравом значении по умолчанию , так что если это произойдет, то код потерпит неудачу громко и быстро. Но лучше всего потерпеть неудачу во время компиляции!
Показать ещё 8 комментариев
Теги:
initialization
constructor-injection

3 ответа

4
Лучший ответ

Просто иметь простой обертку может работать:

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

Теперь типы являются явными, и вы не можете их обменивать без получения ошибки компилятора.

  • 2
    -1 За исключением того, что Val1 и Val2 относятся к Val1 и Val2 же типу, поэтому они взаимозаменяемы.
  • 0
    Вот демонстрация того, что я имею в виду . Такой подход просто ничего не делает.
Показать ещё 6 комментариев
2

Очень любопытно узнать, что делает 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 (или он побеждает точку), но это помогает.

  • 1
    Не самая плохая идея. Обратите внимание, что если вы реализуете базовый template <typename T> class Wrapper ... то вы можете просто унаследовать это и ввести базовый конструктор с using . Тогда Val1 и Val2 могут быть почти однострочными.
0

Что-то вроде этого (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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню