6

受到 Herb Sutter 引人入胜的演讲Not your Father's C++的启发,我决定使用 Microsoft 的 Visual Studio 2010 再看看最新版本的 C++。我对 Herb 关于 C++ 是“安全”的断言特别感兴趣,因为我没有听说过 C ++11 解决了众所周知的向上 funarg 问题。据我所知,C++11 没有解决这个问题,因此不是“安全的”。

您不想返回对局部变量的引用,因为局部变量是在函数返回后将不再存在的堆栈帧上分配的,因此,函数将返回一个指向已分配内存的悬空指针,这将导致非-确定性数据损坏。C 和 C++ 编译器知道这一点,并在您尝试返回指向本地的引用或指针时警告您。例如,这个程序:

int &bar() {
  int n=0;
  return n;
}

导致 Visual Studio 2010 发出警告:

warning C4172: returning address of local variable or temporary

但是,C++11 中的 lambda 可以很容易地通过引用捕获局部变量并返回该引用,从而产生等效的悬空指针。考虑以下函数foo,它返回一个捕获局部变量n并返回它的 lambda 函数:

#include <functional>

std::function<int()> foo(int n) {
  return [&](){return n;};
}

这个看似无害的函数是内存不安全的,也是损坏数据的来源。调用此函数以在一个位置获取 lambda,然后调用 lambda 并在另一个位置打印其返回值,将为我提供以下输出:

1825836376

此外,Visual Studio 2010 没有给出警告。

对我来说,这看起来像是语言中一个非常严重的设计缺陷。即使是最简单的重构也可以使 lambda 跨堆栈帧,默默地引入非确定性数据损坏。然而,关于这个问题的信息似乎很少(例如在 StackOverflow 上搜索“upwards funarg”和 C++ 没有得到任何结果)。人们是否意识到这一点?是否有人正在研究解决方案或描述解决方法?

4

2 回答 2

3

这不是特定于 lambdas 的,当涉及到生命周期时,你可以做很多坏事(你已经注意到了至少一种情况)。尽管与 C++03 相比,C++11 在某些方面可能更安全,但 C++ 并未强调内存安全。

这并不是说 C++想要安全,但我想说的是“不要为你不使用的东西买单”通常会妨碍添加安全防护(不考虑像可能会阻止对所有无效程序发出诊断的问题)。如果你能解决向上的函数问题而不影响其他所有情况的性能,那么标准委员会就会感兴趣。(我并不是说,我认为这是一个有趣且困难的问题。)

由于您似乎正在追赶,那么到目前为止作者(和其他人)的智慧通常是避免对 lambda 表达式(例如[&, foo, bar])使用按引用捕获全部捕获,并且要小心 by-一般参考捕获。您可以将 lambda 表达式的捕获列表视为 C++ 中必须注意生命周期的另一个地方;或者另一种观点是将 lambda 表达式视为函子的对象文字符号(实际上它们是这样指定的)。当你设计一个关于生命周期的类类型时,你必须小心:

struct foo {
    explicit foo(T& t)
        : ref(t)
    {}

    T& ref;
};

foo make_foo()
{
    T t;
    // Bad
    return foo { t };
    // Not altogether different from
    // return [&t] {};
}

在这方面,lambda 表达式不会改变编写“明显”错误代码的现状,并且它们继承了所有预先存在的警告。

于 2012-04-12T12:37:10.067 回答
2

如果你试图让自己幸福地不知道内存处理,你就不能简单地在任何复杂的 C++ 项目中工作。有数百种语言更好地针对这种范式。C++没有垃圾回收是有原因的;它确实不适合您要使用 C++ 的场景

据说,在您的 lambda 示例中,一个简单的更改将使您的 lambda 示例完全安全:

#include <functional>

std::function<int()> foo(int n) {
  return [=](){return n;}; //now n is copied by value
}
于 2012-04-12T17:16:46.490 回答