【问题标题】:Why is it not possible to use private method in a lambda?为什么不能在 lambda 中使用私有方法?
【发布时间】:2012-08-13 12:06:19
【问题描述】:

有这样的课程:

class A {
public:
    bool hasGrandChild() const;

private:
    bool hasChild() const;
    vector<A> children_;
};

为什么不能像这样在方法hasGrandChild()中定义的lambda表达式中使用私有方法hasChild()

bool A::hasGrandChild() const {
    return any_of(children_.begin(), children_.end(), [](A const &a) {
        return a.hasChild();
    });
}

编译器发出一个错误,指出方法hasChild() 在上下文中是私有的。有什么解决办法吗?

编辑: 看来我发布的代码最初是有效的。我以为是等价的,但是does not work on GCC的代码更像这样:

#include <vector>
#include <algorithm>

class Foo;

class BaseA {
protected:
    bool hasChild() const { return !children_.empty(); }
    std::vector<Foo> children_;
};

class BaseB {
protected:
    bool hasChild() const { return false; }
};

class Foo : public BaseA, public BaseB {
public:
  bool hasGrandChild() const {
    return std::any_of(children_.begin(), children_.end(), [](Foo const &foo) {
        return foo.BaseA::hasChild();
      });
  }  
};

int main()
{
  Foo foo;
  foo.hasGrandChild();
  return 0;
}

似乎this does not work 的完全限定名称存在问题,但this works

【问题讨论】:

  • 闭包类型和你的A类没有关系,所以自然不能访问A的非公开成员。也不可能,因为它的类型名称是不可知的,所以你甚至不能把它设为friend
  • 只有我还是在 gcc 上有效? ideone.com/333qw
  • @pmr: 是的,它似乎适用于旧 GCC,但不适用于较新的 GCC。
  • @JurajBlaho 它适用于 4.7.1。更新是指 4.8?
  • 它在 4.6.2 中对我不起作用。也许这是一个错误。我应该更新。

标签: c++ c++11 lambda


【解决方案1】:

当 lambda 尝试使用完全限定名从父类访问受保护成员时,这似乎只是一个 GCC 错误。 This does not work:

class Base {
protected:
    bool hasChild() const { return !childs_.empty(); }
    std::vector<Foo> childs_;
};

class Foo : public Base {
public:
  bool hasGrandChild() const {
    return std::any_of(childs_.begin(), childs_.end(), [](Foo const &foo) {
      return foo.Base::hasChild();
    });
  }  
};

,但是this works

class Foo : public Base {
public:
  bool hasGrandChild() const {
    return std::any_of(childs_.begin(), childs_.end(), [](Foo const &foo) {
      return foo.hasChild();
    });
  }  
};

根据 C++11, 5.1.2/3:

lambda 表达式的类型(也是 闭包对象)是一个唯一的、未命名的非联合类类型——称为 闭包类型——其属性如下所述。此类类型 不是一个集合(8.5.1)。 闭包类型在 包含的最小块作用域、类作用域或命名空间作用域 对应的 lambda 表达式

然后是 C++11、11.7/1:

嵌套类是一个成员,因此具有相同的访问权限 任何其他成员。

所以提到的函数局部 lambda 应该与类的任何其他成员具有相同的访问权限。因此它应该能够从父类调用受保护的方法。

【讨论】:

  • 很好地解释了为什么它应该起作用。 (以及为什么这里的大多数其他答案都是错误的。)+1
【解决方案2】:

标准(C++11,§5.1.2/3)声明

lambda 表达式的类型(也是 闭包对象)是一个唯一的、未命名的非联合类类型——称为 闭包类型

由于它是一个独特的类类型,它不是Afriend,它无权访问A 的私有成员。

编译器在这里所​​做的是创建一个类类型,该类类型具有适当的成员来存储任何捕获的变量、适当的operator() 等——如果你想在 C++03 中模拟 lambda,这正是你自己编写的.这种类型肯定无法访问private 成员,这可能更容易理解为什么存在限制以及为什么没有解决方法

更新关于可能的解决方法:

最好说“没有使用 lambda 的变通方法”,因为通常变通方法确实存在,尽管它们要求您放弃方便的 lambda 语法。例如,您可以:

  1. 编写一个本地类类型,显式捕获this 以及它需要的任何其他本地变量(受以下 Björn Pollex 评论的启发)。
  2. 编写 private 方法而不是 lambda 并将其作为回调传递(例如,为方便起见,使用 std::bind)。如果您想捕获除this 之外的本地人,您可以在呼叫站点使用更多std::bind 来执行此操作。

【讨论】:

  • 它可以与本地 struct 一起使用 (ideone.com/AvsBE)。你能解释一下区别吗?
  • @BjörnPollex:这是一个 local struct,这就是为什么它可以访问其包含类型的私有成员(引用本书:本地类位于封闭范围的范围内,并且与封闭函数具有相同的对函数外部名称的访问权限。)。不过,您可以说这是一种实用的解决方法。
  • @Jon:这并不能解释为什么编译器不简单地让闭包类型成为它所属的类的朋友。如果它可以隐式捕获this,从而有效地充当成员函数,它应该能够访问其他私有成员。
  • @NicolBolas:没有技术原因不能默认设置为friend,但对我来说这听起来不是一个好主意。如果您需要访问私有成员,还有更详细的解决方法不会破坏封装(将很快更新答案)。
  • 您提到的草稿版本是否不包含以下内容,或者您​​是否出于某种原因将其排除在答案之外? 其属性如下所述。此类类型不是聚合 (8.5.1)。闭包类型在包含相应 lambda 表达式的最小块作用域、类作用域或命名空间作用域中声明。 我发现它非常相关,尤其是与 Juraj 也提到的嵌套类的 §11.7 相关。
【解决方案3】:

解决方法:

typedef  bool (A::*MemFn)(void) const;

bool A::hasGrandChild() const {
    MemFn f = &A::hasChild;
    return any_of(childs_.begin(), childs_.end(), [=](A const &a) {
            return (a.*f)();
    });
}

【讨论】:

  • 等等。你为什么不干脆做any_of(begin, end, std::mem_fn(&amp;A::hasChild))呢?除了轻微的性能损失。
  • @pmr - 这主要是为了说明如何在 lambda 表达式中访问私有成员函数。
  • @pmr:性能损失?我不指望一个;对于一个合理的编译器来说,f 不会改变是很明显的。
  • @MSalters 我也没想到会这样。在使用mem_fn 时,我体验并测量了一个小的性能损失。但也许我错了。
【解决方案4】:

您可以显式捕获this 并使其成为可以访问私有成员的“成员 lambda”。

例如,考虑以下示例:

#include <iostream>
class A {
private:
    void f() { std::cout << "Private"; }
public:
    void g() { 
        [this] { 
            f(); 
            // doesn't need qualification 
        }(); 
    }
};
class B {
private:
    void f() { std::cout << "Private"; }
public:
    void g() { [] { f(); }(); } // compiler error
};
int main() {
    A a;
    a.g();
}

【讨论】:

    【解决方案5】:

    这是不可能的,因为 lambda 不是类的一部分。这与创建类外函数并调用它而不是创建 lambda 相同。当然它不能访问私有成员。

    【讨论】:

      猜你喜欢
      • 2023-03-08
      • 2011-06-21
      • 2013-05-08
      • 2014-06-03
      • 1970-01-01
      • 1970-01-01
      • 2015-06-26
      • 2016-12-05
      • 2019-07-14
      相关资源
      最近更新 更多