【问题标题】:How to guard against function arguments being passed in the wrong order?如何防止函数参数以错误的顺序传递?
【发布时间】:2014-06-08 07:38:32
【问题描述】:

假设我有一个如下所示的 C++ 函数:

double myfunction(double a, double b) {
    // do something
}

然后我这样称呼:

double a = 1.0;
double b = 2.0;
double good_r = myfunction(a, b);
double bad_r = myfunction(b, a); // compiles fine

我想确保ab 永远不会以错误的顺序提供。 在 C++ 中确保这一点的最佳方法是什么?

其他语言允许命名参数,如下所示:

double good_r = myfunction(a=a, b=b);
double bad_r = myfunction(a=b, b=a); // mistake immediately obvious
double bad_r = myfunction(b=b, a=a); // compiles fine

或者也许可以使用类型部分解决问题,即

double my_type_safe_function(a_type a, b_type b) {
    // do something
}
a_type a = 1.0;
b_type b = 2.0;
double good_r = myfunction(a, b);
double bad_r = myfunction(b, a); // compilation error

编辑:有几个人问我“错误的顺序”是什么意思。我的意思是,在实际代码中ab 有一定的意义。例如,参数可能改为heightwidth。它们之间的区别对于函数返回正确结果非常重要。但是,它们都是浮点数,并且它们都具有相同的尺寸(即长度)。此外,他们没有“明显”的顺序。编写函数声明的人可以假设(width, height),而使用函数的人可以假设(height, width)。我想要一种方法来确保不会错误地发生这种情况。使用两个参数很容易注意顺序,但在大型项目中,最多有 6 个参数,错误就会蔓延。

理想情况下,我希望在编译时完成检查,并且不会影响性能(即在一天结束时,它们被视为普通的旧浮点数或其他)。

【问题讨论】:

  • ab 之间的区别是什么导致它们的顺序错误?您是否可以验证它们的实际值,并在它们无效时提前抛出异常/退出?如果它们都是同一类型,则没有真正万无一失的方法可以在编译时阻止它。

标签: c++ types arguments named-parameters


【解决方案1】:

我认为您已经提供了最简单的解决方案,使用类型。

一种替代方法是使用构建器类和方法链。 喜欢:

class MyfunctionBuilder {
  MyFunctionBuilder & paramA(double value);
  MyFunctionBuilder & paramB(double value);
  double execute();
  (...)
}

你会这样使用:

double good_r = MyFunctionBuilder().paramA(a).paramB(b).execute();

但是要写很多额外的代码!

【讨论】:

  • 我在这里和 Bgie 在一起——使用类型!
  • 是否可以使用简单的typedefs 来做到这一点?
  • 不这么认为,我认为你需要小类/结构,并注意隐式构造(使构造函数从双重显式)
【解决方案2】:

究竟什么是“错误的顺序”?在你的这个例子中

double myfunction(double a, double b) {
    // do something
}

double a = 1.0;
double b = 2.0;
double good_r = myfunction(a, b);
double bad_r = myfunction(b, a);

你真的想知道这个正确的顺序吗?如果变量被命名为“quapr”和“moo”而不是“a”和“b”怎么办?那么光看顺序就无法猜对错了。

考虑到这一点,您至少可以做两件事。首先,是给参数起有意义的名字,例如

float getTax( float price, float taxPercentage )

而不是

float getTax( float a, float b )

其次,在里面做必要的检查:

float divide( float dividend, float divisor )
{
    if( divisor == 0 )
    {
         throw "omg!";
    }
}

可以进行更复杂的检查,例如制作函子并显式设置其参数,但在大多数情况下,这只会使事情复杂化而没有太多好处。

【讨论】:

  • 在很多情况下,必须以正确的顺序传递两个(或更多)相同类型的值:例如创建一个矩形(顶部,左侧,宽度,高度......或顶部,左侧,底部,右侧!)。您无法通过断言式检查可靠地检测到那里的错误。
  • @AAT,'在很多情况下,必须以正确的顺序传递两个(或更多)相同类型的值'。怎么样:每个函数都希望以一个正确的顺序获取参数。然而,我仍然需要看到一个仿函数(就像在 Bgie 的回答中一样)来创建一个矩形。 IMO,在 99.9% 的情况下,有意义的参数名称和关注最少的程序员就足够了。真的,有人会在不关心参数顺序的情况下调用函数吗?
  • 哦,我同意,C/C++ 中的正确答案必须是命名良好的参数(和变量)。函子和其他人工构造之类的东西通常太麻烦了。我只是想强调,在很多情况下这是一个潜在问题,而断言式检查无济于事。
  • Ada(我多年前学过,但从未在愤怒中使用过)有一个解决这个问题的方法,它是“廉价”的自定义类型和非常强大的类型检查。您可以创建“自己的”整数或双精度:例如,您可以有一个“Top”,它只是一个整数,但只能传入声明为“Top”的值,并且不能与“底部”。我猜你可以通过将值装箱到结构中来近似 C/C++。
【解决方案3】:

这个怎么样:

struct typeAB {float a; float b; };

double myfunction(typeAB p) {
// do something
  return p.a - p.b;
}

int main()
{
  typeAB param;
  param.a = 1.0;
  param.b = 2.0;
  float result = myfunction(param);
  return 0;
}

当然,您在分配参数时仍然可能会搞砸,但这种风险很难避免:)

【讨论】:

  • 我喜欢这个解决方案,但恕我直言,复制对性能有(小)影响。
  • @Bgie 您是否分析了您的代码以得出这个结论,或者您只是在猜测编译器在做什么?
  • @DDrmmr 是的,我猜 :-)
【解决方案4】:

一种变体是每个“新”类型有一个结构,然后使用宏在优化的构建中使它们消失。

这些方面的东西(只是稍微测试过,所以它可能会被淘汰):

#define SAFE 0

#if SAFE
#define NEWTYPE(name, type) \
    struct name { \
       type x; \
       explicit name(type x_) : x(x_) {}\
       operator type() const { return x; }\
    }
#else
#define NEWTYPE(name, type) typedef type name
#endif

NEWTYPE(Width, double);
NEWTYPE(Height, double);

double area(Width w, Height h)
{
    return w * h;
}

int main()
{
    cout << area(Width(10), Height(20)) << endl;

    // This line says 'Could not convert from Height to Width' in g++ if SAFE is on.
    cout << area(Height(10), Width(20)) << endl; 
}

【讨论】:

    猜你喜欢
    • 2023-04-08
    • 1970-01-01
    • 2021-05-25
    • 2010-11-23
    • 2013-01-04
    • 1970-01-01
    • 1970-01-01
    • 2014-04-27
    • 1970-01-01
    相关资源
    最近更新 更多