为什么命名参数不经常使用?

时间:2012-04-05 23:57:22

标签: c++ named-parameters

我设计了一个参数类,允许我编写如下代码:

//define parameter
typedef basic_config_param<std::string> name;

void test(config_param param) {

  if(param.has<name>()) { //by name
    cout << "Your name is: " << param.get<name>() << endl;
  }

  unsigned long & n = param<ref<unsigned long> >(); //by type
  if(param.get<value<bool> >(true)) { //return true if not found
    ++n;
  }
}


unsigned long num = 0;
test(( name("Special :-)"), ref<unsigned long>(num) )); //easy to add a number parameter
cout << "Number is: " << num; //prints 1

该类的性能非常快:一切都只是堆栈的引用。为了保存所有信息,我在使用最多5个参数的内部缓冲区进行堆分配之前减少每个对象的大小,但这很容易改变。

为什么不经常使用这种语法,重载operator,()来实现命名参数?是因为潜在的性能损失?

另一种方法是使用命名的习语:

object.name("my name").ref(num); //every object method returns a reference to itself, allow object chaining.

但是,对我来说,重载operator,()看起来更“现代”的C ++,只要你不忘记使用双括号。即使它比正常功能慢,性能也不会受到太大影响,因此在大多数情况下它可以忽略不计。

我可能不是第一个想出这样的解决方案的人,但为什么它不常见?在我写一个接受它的类之前,我从未见过像上面的语法(我的例子),但对我来说看起来很完美。

2 个答案:

答案 0 :(得分:58)

  

我的问题是为什么不再使用这种语法,重载operator,()来实现命名参数。

因为它是反直觉的,非人类可读的,可以说是一种糟糕的编程习惯。除非您想破坏代码库,否则请避免这样做。

test(( name("Special :-)"), ref<unsigned long>(num) ));

我们说第一次看到这段代码片段。我的思维过程是这样的:

  1. 乍一看,它看起来像是"the most vexing parse"的一个例子,因为你使用了双括号。所以我假设测试是一个变量,并且不得不怀疑你是否忘记编写变量的类型。然后我发现这件事实际上是编译的。之后,我不得不怀疑这是否是一个立即销毁的类型测试类的实例,并且您对所有类类型使用小写名称。
  2. 然后我发现它实际上是一个函数调用。大。
  3. 现在,代码片段看起来像是一个带有两个参数的函数调用。
  4. 现在很明显,这不能是带有两个参数的函数调用,因为你使用了双括号。
  5. 所以,现在我必须弄清楚()内的情况。
  6. 我记得有一个逗号运算符(我在过去的5年中从未在真正的C ++代码中见过)丢弃了之前的参数。现在我不得不想知道name()的有用副作用是什么,以及name()是什么 - 函数调用或类型(因为你不使用大写/小写字母来区分类/函数(即Test是一个类,但test是一个函数),并且您没有C个前缀。)
  7. 在源代码中查找name后,我发现它是类。并且它会使,运算符超载,因此它实际上不再丢弃第一个参数。
  8. 看看这里浪费了多少时间?坦率地说,写这样的东西可能会让你遇到麻烦,因为你使用语言特性使你的代码看起来像你的代码实际上做的不同(用一个参数进行函数调用看起来有两个参数或那个它是一种可变函数)。这是一个糟糕的编程习惯,大致相当于重载operator +来执行替代而不是添加。

    现在,让我们考虑QString示例。

     QString status = QString("Processing file %1 of %2: %3").arg(i).arg(total).arg(fileName);
    

    让我们说,这是我生命中的第一次。这就是我的思维过程:

    1. 有一个QString类型的变量status
    2. 它是从QString()类型的临时变量初始化的。
    3. ...在调用QString :: arg方法之后。 (我知道这是一种方法)。
    4. 我在文档中查找.arg以查看其功能,并发现它替换%1 - 样式条目并返回QString&amp ;.因此,.arg()电话链立即有意义。请注意,QString :: arg之类的东西可以模板化,并且您可以为不同的参数类型调用它,而无需在<>中手动指定参数类型。
    5. 那个代码片段现在有意义,所以我继续使用另一个片段。
    6.   

      看起来更像&#34;现代&#34; C ++

      &#34;新颖有光泽&#34;有时意味着&#34;马车和破坏&#34; (slackware linux建立在一个类似的想法上)。如果您的代码看起来很现代,则无关紧要。它应该是人类可读的,它应该做它想要做的事情,你应该浪费尽可能少的时间来编写它。即你应该(个人推荐)的目标是以最低的成本(包括维护)在最短的时间内实现最大量的功能,但是获得最大的奖励。遵循KISS原则也是有道理的。

      你的&#34;现代&#34;语法不会降低开发成本,不会缩短开发时间,并且会增加维护成本(反直觉)。因此,应避免使用此语法。

答案 1 :(得分:3)

没有必要。您的动态调度(行为方式不同,取决于参数的逻辑类型)可以更容易实现a)b)使用模板专业化更快。

如果您确实需要基于仅在运行时可用的信息进行区分,我会尝试将您的test函数移动为param类型的虚拟方法,并简单地使用动态绑定(这就是它的用途,这就是你重新发明的东西)。

这种方法更有用的唯一情况可能是多调度方案,您希望减少代码并找到一些相似性模式。