在不依赖职位的情况下强制论证

时间:2014-11-11 19:13:58

标签: c++ initialization constructor-injection

假设一个类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's 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中的一个字段,这些字段在运行时才会被检测到。结构初始化语法或初始化列表会使忘记字段变得不可能,但是我们又回到了依赖位置!

是否有一些技巧可以充分利用两个世界 - 编译器强制执行的强制参数是按名称而不是位置分配的?

3 个答案:

答案 0 :(得分: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 ) );

现在类型是显式的,你不能在没有编译器错误的情况下交换它们。

答案 1 :(得分: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(或它失败了点),但它有所帮助。

答案 2 :(得分:0)

像这样(未经测试)

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