嵌套类成员函数无法访问封闭类的函数。为什么?

25
请看下面的示例代码:
class A
{
private:
    class B
    {
    public:
        foobar();
    };
public:
    foo();
    bar();
};

在A类和B类的实现中:

A::foo()
{
    //do something
}

A::bar()
{
    //some code
    foo();
    //more code
}

A::B::foobar()
{
    //some code
    foo(); //<<compiler doesn't like this
}

编译器标记了在方法foobar()中调用foo()的代码行。我先前将foo()作为类A的私有成员函数,但现在更改为公共成员函数,认为B的函数无法访问它。当然,这并没有起到帮助的作用。我试图重用A方法提供的功能。为什么编译器不允许这个函数调用?在我看来,它们是同一封闭类(A)的一部分。我认为C++标准中关于嵌套类成员对封闭类可访问性问题已经解决了。
如何在保持B嵌套在A的情况下实现我想做的事情而不必为B重写相同的方法(foo())?
我正在使用VC ++编译器版本9(Visual Studio 2008)。感谢您的帮助。
5个回答

30

foo()是一个属于A的非静态成员函数,你试图在没有实例的情况下调用它。
嵌套类B是一个单独的类,仅具有一些访问权限,并且对现有A实例没有任何特殊知识。

如果B需要访问A,你必须给它一个引用,例如:

class A {
    class B {
        A& parent_;
    public:
        B(A& parent) : parent_(parent) {}
        void foobar() { parent_.foo(); }
    };
    B b_;
public:
    A() : b_(*this) {}
};

5
+1,只是一个小细节 - 在这里,parent 可能不是最好的成员变量名 - 它很容易与继承混淆。 - Nikolai Fetissov
2
我只是想提一下,嵌套类 B 拥有对类 A 的引用是基于非常好的设计原因。 - manifest
谢谢您提供带有示例的解释。我猜测C++标准11.8已经改变,涉及嵌套类的类成员访问。我知道gcc允许通过嵌套类进行访问(我对此非常确定),但是MS VC编译器不支持。嗯...有趣。 - Rahul
@Rahul:是的,你有访问权限,但实例之间没有隐式关系——它只是一个类,你仍然需要显式地创建实例。 - Georg Fritzsche
Embarcadero C++Builder编译器不允许内部类访问包含类的私有或受保护方法。它会发出E2247 <方法名称>不可访问的错误。如果我改成public,就可以工作了。我有一个类似的情况,但我不想将包含类的方法暴露给公众。 - Eric
显示剩余2条评论

2

这是一种自动化的技巧,但可能不太可移植(虽然自VC++6.0起可以使用)。为了使其工作,类B必须是类A的成员。

#ifndef OUTERCLASS
#define OUTERCLASS(className, memberName) \
    reinterpret_cast<className*>(reinterpret_cast<unsigned char*>(this) - offsetof(className, memberName))
#endif 

class A
{
private:
    class B
    {
    public:
        void foobar() {
           A* pA = OUTERCLASS(A, m_classB);
           pA->foo();
        }
    } m_classB;
public:
    foo();
    bar();
};

谢谢你,Igor回答我的问题并提供了一个例子。你和Georg的实现方式几乎相似,都有一个对外部类的引用,但是我选择了Georg的回答,因为它更简洁。 - Rahul
1
没关系,毕竟这看起来是一个相当恶劣的黑客。话虽如此,它一直非常可靠,而且有点有趣 :) - Igor Zevaka

1

基本上就是Georg Fritzsche所说的。

#include <iostream>
#include <cstring>
using namespace std;

class A
{
private:
    class B
    {
     A& parent_;
     public:
        //B();  //uncommenting gives error
        ~B();
        B(A& parent) : parent_(parent) {}

        void foobar() 
        { 
         parent_.foo();  
         cout << "A::B::foo()" <<endl; 
        }

        const std::string& foobarstring(const std::string& test) const 
        { 
         parent_.foostring(test); cout << "A::B::foostring()" <<endl;
        }
    };
public:
    void foo();
    void bar();
    const std::string& foostring(const std::string& test) const;
    A(); 
    ~A(){};
    B b_;
};

//A::B::B() {}; //uncommenting gives error
A::B::~B(){};

A::A():b_(*this) {}


void A::foo()
{
    cout << "A::foo()" <<endl;
}

const std::string& A::foostring(const std::string& test) const
{
    cout << test <<endl;
    return test;
}

void A::bar()
{
    //some code
    cout << "A::bar()" <<endl;
    foo();
    //more code
}

int main(int argc, char* argv[])
{
A a;
a.b_.foobar();
a.b_.foobarstring("hello");

return 0;
}

如果您取消注释默认的B构造函数,将会出现错误。

@cbinder 请尝试取消注释 A::B::B() {}; - enthusiasticgeek
如果您想使用B()类构造函数,则可以使用reinterpret_cast。您可以参考此链接:https://dev59.com/6HA75IYBdhLWcg3w3NLf#68117065 - Puneeth G R

0

结合Igor Zevaka和enthusiasticgeek的答案。 如果您使用new关键字创建类成员变量,则还可以使用reinterpret_cast来计算偏移量:

#include <iostream>
#include <cstring>
using namespace std;

template < typename T, typename U > constexpr size_t offsetOf(U T:: *member)
{
    return (char*) &((T*) nullptr->*member) - (char*) nullptr;
}

class A
{
    private:
        class B
        {
         public:
            B(string message);
            ~B();

            void foobar()
            {
                A *pA = reinterpret_cast<A*> (reinterpret_cast< unsigned char*> (this) - offsetOf(&A::b_));
                pA->foo();
                pA->bar();
                std::cout << "DONE!";
            }
        };
    public:
        void foo();
        void bar();
        A();
        ~A() {};
        B* b_ = new B("Hello World!");
};

A::A() 
{
    cout << "A constructor\n";
};
A::B::B(string message) {
    cout << "B constructor\n";
    cout << "Message =  " << message << "\n";
};
A::B::~B() {};

void A::foo()
{
    cout << "A::foo()" << endl;
}

void A::bar()
{
    cout << "A::bar()" << endl;
    foo();
}

int main(int argc, char *argv[])
{
    A* a = new A();
    a->b_->foobar();

    return 0;
}

输出:

B constructor
Message =  Hello World!
A constructor
A::foo()
A::bar()
A::foo()
DONE!

参考资料:

https://dev59.com/6HA75IYBdhLWcg3w3NLf#10607424

https://dev59.com/6HA75IYBdhLWcg3w3NLf#3058382

https://dev59.com/DGYs5IYBdhLWcg3wDfp5#20141143


0
如果您想要重用 A 的功能,那么应该从 A 继承,而不是将 B 嵌套在其中。

1
我不会在B中扩展A的功能,因此没有必要从A继承B。此外,我希望将B对于A类的用户隐藏。 - Rahul

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