派生类无法访问基类protected成员的原因及友元相关困惑
代码示例
#include <string> #include <vector> using namespace std; class Message { protected: virtual string json() const = 0; }; class _messageChain_ final: public Message { public: string json() const override; private: vector<Message *>messages; }; string _messageChain_::json() const { string json; for ( Message *msg : messages ) json += msg->json(); return json; } int main ( void ) { }
编译错误
main.cpp: In member function ‘virtual std::string _messageChain_::json() const’: main.cpp:21:21: error: ‘virtual std::string Message::json() const’ is protected within this context 21 | json += msg->json(); | ^ main.cpp:7:17: note: declared protected here 7 | virtual string json() const = 0;
我的困惑
原本认为protected修饰符会让派生类_messageChain_在其json()成员函数中能够访问基类Message的json()成员,但实际并非如此。将_messageChain_设为Message的友元类,或者将json()改为public修饰时代码可以正常编译;但仅将_messageChain_的json()函数设为Message的友元时无法解决问题。想了解其中的原因。
1. 派生类访问protected成员的规则限制
你对protected成员的访问逻辑理解有误。C++中,派生类确实能访问基类的protected成员,但仅限访问当前派生类对象自身的基类部分——简单说,你可以在_messageChain_的成员函数里直接调用this->json()(这是访问自己的基类部分),但不能通过基类指针/引用去访问其他独立对象(哪怕这个对象实际是派生类实例)的基类protected成员。
在你的代码里,msg是Message*类型,编译器仅根据静态类型(Message*)判断权限:它认为你在尝试访问一个基类对象的protected成员,而派生类没有权限这么做,因此抛出编译错误。哪怕msg实际指向的是_messageChain_对象,编译器也不会因为动态类型而放宽权限判断。
2. 友元类能解决问题的原因
当你把_messageChain_声明为Message的友元类后,_messageChain_的所有成员函数都获得了访问Message类任意对象的protected/private成员的权限。此时不管msg是基类指针还是派生类指针,调用msg->json()都符合权限规则,自然能编译通过。
3. 单独将_messageChain_::json()设为友元无效的原因
几乎可以肯定是你友元声明的语法不正确,导致友元关系未生效。正确的友元函数声明需要满足两个条件:
- 提前声明
_messageChain_类:在Message类定义前,必须先添加class _messageChain_;,告诉编译器该类的存在。 - 友元函数签名完全匹配:在
Message类的定义中,友元声明要写成friend string _messageChain_::json() const;,返回值、函数名、const修饰符一个都不能错。
如果你的声明没满足这两点,编译器会无法识别这个友元关系,依然会抛出权限错误。假设你正确完成了声明,这个友元函数是有权限调用msg->json()的。
额外思路
如果你的messages容器中只会存储_messageChain_类型的对象,也可以考虑将msg安全转型为_messageChain_*,这样调用json()时,编译器会认为你在访问派生类自身的成员(虽然它重写了基类的方法),权限上是允许的:
// 在_messageChain_::json()中修改循环部分 for ( auto msg : messages ) json += static_cast<_messageChain_*>(msg)->json();
不过这种方式需要确保转型的安全性(比如用dynamic_cast做检查),且不如调整访问权限或使用友元优雅。
内容的提问来源于stack exchange,提问作者sixsixqaq

