9

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

//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++,只要你不要忘记使用双括号。即使它比正常功能慢,性能也不会受到太大影响,因此在大多数情况下可以忽略不计。

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

4

2 回答 2

59

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

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

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

假设我第一次看到这个代码片段。我的思考过程是这样的:

  1. 乍一看,它看起来像是“最令人烦恼的解析”的示例,因为您使用了双括号。所以我假设 test 是一个变量,并且想知道你是否忘记写变量的类型。然后我突然想到这个东西实际上可以编译。在那之后,我不得不怀疑这是否是一个立即销毁的类型测试类的实例,并且您对所有类类型都使用小写名称。
  2. 然后我发现它实际上是一个函数调用。伟大的。
  3. 代码片段现在看起来像一个带有两个参数的函数调用。
  4. 现在对我来说很明显这不能是带有两个参数的函数调用,因为您使用了双括号。
  5. 所以,现在我必须弄清楚里面到底发生了什么()
  6. 我记得有一个逗号运算符(在过去 5 年中我从未在真正的 C++ 代码中看到过)它丢弃了前面的参数。所以现在我不得不想知道 name() 的有用副作用是什么,以及 name() 是什么 - 函数调用或类型(因为您不使用大写/小写字母来区分类/函数(即Test是一个类,但是test是一个函数),并且你没有C前缀)。
  7. 在源代码中查找后name,我发现它是类。并且它使,运算符重载,因此它实际上不再丢弃第一个参数。

看看这里浪费了多少时间?坦率地说,写这样的东西会给你带来麻烦,因为你使用语言特性使你的代码看起来与你的代码实际所做的不同(你用一个参数进行函数调用看起来有两个参数,或者它是一个可变参数函数)。这是一种不好的编程习惯,大致相当于重载 operator+ 以执行减法而不是加法。

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

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

假设我有生以来第一次看到它。我的思考过程是这样的:

  1. 有一个statusQString 类型的变量。
  2. 它是从一个 QString() 类型的临时变量初始化的。
  3. ...在调用 QString::arg 方法之后。(我知道这是一种方法)。
  4. 我在文档中查找.arg以查看它的作用,并发现它替换了%1-style 条目并返回 QString&。因此,调用链.arg()立即变得有意义。请注意,可以对 QString::arg 之类的东西进行模板化,并且您可以为不同的参数类型调用它,而无需在<>.
  5. 该代码片段现在有意义了,所以我转到另一个片段。

看起来更“现代”的 C++

“新的和闪亮的”有时意味着“有问题的和坏的”(slackware linux 是建立在一个有点类似的想法上的)。如果您的代码看起来很现代,这无关紧要。它应该是人类可读的,它应该做它打算做的事情,并且你应该在编写它时浪费尽可能少的时间。即您应该(个人建议)旨在“以最少的成本(包括维护)在最少的时间内实现最多的功能”,但这样做可以获得最大的回报。遵循 KISS 原则也是有意义的。

您的“现代”语法不会降低开发成本,不会减少开发时间,并且会增加维护成本(违反直觉)。因此,应该避免这种语法。

于 2012-04-06T01:18:04.220 回答
3

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

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

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

于 2012-04-06T00:12:06.980 回答