假设一个类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中的一个字段,这些字段在运行时才会被检测到。结构初始化语法或初始化列表会使忘记字段变得不可能,但是我们又回到了依赖位置!
是否有一些技巧可以充分利用两个世界 - 编译器强制执行的强制参数是按名称而不是位置分配的?
答案 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