如何将这段Javascript代码重写为C++11?

3

以下是我在 Javascript Definitive Guide 中看到的 JavaScript 闭包代码。我想将其改写成 C++11 代码。

var uniqueID1 = (function()
{
    var id = 0;
    return function() { return id++; };
})();    

这是我编写的cpp代码,但它无法编译。C++11能够表示同样的表达式吗?

auto c = []() -> int (*)() { int x = 0; return [&x]() -> int { return x++; }};

我正在使用VS2010。

编辑:这是我编写的完整JavaScript示例代码。您可以轻松测试代码在您的Web浏览器上运行的情况。

<head>
<script language="javascript">
var uniqueID1 = (function()
{
    var id = 0;
    return function() { return id++; };
})();

var uniqueID2 = (function()
{
      var id = 0;
      return function() { return id++; };
})();
</script>
</head>

<body>
        <input value = "uniqueid1" type="button" OnClick="alert(uniqueID1());"></input>
        <input value = "uniqueid2" type="button" OnClick="alert(uniqueID2());"></input>
</body>
</html>

2
哇,那是我见过的最不像C++的C++。我甚至不知道在任何C衍生语言中都可以通过值返回新创建的函数。 编辑:正如Nicol Bolas所提到的,你不能这样做。 - Borealid
2个回答

19

好的,首先让我们分解一下你的 JavaScript 做了什么。

function()
{
    var id = 0;
    return function() { return id++; };
}

这段代码创建了一个没有名称、不带参数的函数。当调用该函数时,会返回一个新的函数。内部函数将可以访问局部作用域变量,并且该变量将成为内部函数的状态的一部分。

每次调用该函数,它都会返回一个函数。该新函数将具有自己的状态(毕竟 var id = 0; 要发挥作用)。每次外部函数的调用都将返回一个具有新状态的新函数。

代码示例:

(function()
{
    var id = 0;
    return function() { return id++; };
})

用一对括号将函数包起来。这是为了运算符优先级规则而需要的,以便使以下内容正常工作:

(function()
{
    var id = 0;
    return function() { return id++; };
})()

这段代码创建了一个函数,并在最后通过()调用了这个函数。这样做的目的就是调用外部函数,让它创建一个拥有自己作用域的内部函数并返回。

在这个表达式中,外部函数已经消失了。它已经不存在了,你无法再访问它。你只是为了调用它而创建了它,然后让它被垃圾回收器丢到了垃圾桶里。

综上所述,我们可以看出以下语句的含义:

var uniqueID1 = (function()
{
    var id = 0;
    return function() { return id++; };
})();

创建外层函数,调用返回新函数的函数,然后将内部函数存储在名为uniqueID1的变量中。

如果您需要证明这是真实的,请在您的HTML中尝试执行此操作:

var uniqueMaker = function()
{
    var id = 0;
    return function() { return id++; };
};

var uniqueID1 = uniqueMaker();
var uniqueID2 = uniqueMaker();
你会得到与你复制粘贴版本相同的答案。
如果我们想让C++代码模拟这个过程,我们需要使用正确的C++代码执行每个步骤。
首先是内部函数。在C++中,不存在词法作用域。因此,您不能返回引用其他作用域变量的函数。您只能返回一个带有另一个作用域变量的副本的lambda函数。此外,除非您明确允许,否则lambda函数不能修改作用域。因此,内部代码应该像这样:
int id = 0;
return [=]() mutable { return ++id; };

这很好,但现在我们需要创建一个lambda函数来返回这个具有状态的lambda函数。在C++中,具有状态的函数与函数指针不同。由于lambda的类型是由编译器定义的,因此无法为其命名。因此,我们必须使用std::function作为外部函数的返回类型。

[]() -> std::function<int()>
{
  int x = 0;
  return [=]() mutable { return x++; };
}

这将创建一个Lambda函数,当调用它时,它将返回一个具有自己状态的内部函数。该状态与对此函数的任何后续调用无关。就像JavaScript示例一样。

现在,仅此还不够。请记住,您的JavaScript创建了外部函数,调用它并仅存储其返回值。外部函数本身被丢弃。所以我们需要模仿这个过程。幸运的是,在C++中可以很容易地实现这一点;它看起来就像JavaScript:

([]() -> std::function<int()>
{
  int x = 0;
  return [=]() mutable { return x++; };
})()

最后,我们将其存储在变量中:

auto uniqueID1 = ([]() -> std::function<int()>
{
  int x = 0;
  return [=]() mutable { return x++; };
})();

uniqueID1 的类型将是 std::function<int()>,而不是外部函数的类型。外部函数只是一个用于创建内部作用域的临时对象。


1
@Benjamin:那就把它变成可变的,就像编辑后的版本一样 ;) - Nicol Bolas
1
@Benjamin 嗯,0 0 0 是正确的响应,如果你想要它打印 0 1 2 那么你应该将 x 声明为静态变量 static int x =0; 或者保存 c() 的结果,因为每次调用 c() 实际上都会将 x=0 - PeterT
1
@Benjamin:当然它返回 0 0 0c() 返回一个函数,该函数存储状态。每次调用 c() 都会返回一个具有自己的独立状态的不同函数。除非我读错了你的 JavaScript 代码,这就是你的 JavaScript 代码所做的。请注意,你的 JavaScript 以 () 结尾,这会调用外部函数。因此,uniqueID1 存储由外部函数返回的函数。有关详细信息,请参见我的编辑。 - Nicol Bolas
2
@Benjamin:我已经完全重写了我的答案,以解释JavaScript代码的工作原理以及如何在C++中复制其效果。 - Nicol Bolas
我认为这是对JavaScript代码的逐字翻译,而且它充分利用了C++11的特性。不幸的是,它很丑陋,不符合惯用语法,并且对于其预期目的过于复杂。例如:立即调用的外部函数是创建新词法作用域的JavaScript习语。我认为在C++中重复使用该习语是不合适的。 - Tim Seguine
显示剩余5条评论

0

我猜你不是要字面翻译,而是想知道如何在C++中最好地表达这个结构。你的JavaScript结构在某种意义上是“伪造一个构造函数”,而你的ID生成器在C++中最好表达为一个简单的。然后你只需要创建该类的实例:

class UniqueID
{
    unsigned int value;
public:
    UniqueID() : value(0) { }
    unsigned int operator()() { return ++value; }
};

使用方法:

UniqueID gen1, gen2;

some_function(gen1());
another_function(gen2());
Foo x(Blue, "Jim", gen1());

这种方法比std::function包装器更轻量级,虽然直接使用lambda表达式也会产生类似的数据结构(但你无法知道它的类型名称)。如果你想让第一个ID为0,你可以将value初始化为-1(尽管将0保留为特殊值可能很有用)。


2
那不是同一件事。如果我正确理解JavaScript,每次调用JavaScript应该返回一个带有状态的新函数实例。 - Nicol Bolas
@NicolBolas:好的,已编辑。多个lambda加上std::function的方法在某种有趣的方式下很有趣,但是它的成本比惯用的翻译要高得多。 - Kerrek SB
4
我不确定是否应该称之为“惯用语”。C++11会有自己的惯用语,Lambda表达式将成为其中的一部分。为此类事情创建一个类是过时的做法。惯用语是会改变的。 - Nicol Bolas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接