在C++编程语言中,友元类是一个特殊的类,它被赋予了访问另一个类私有成员的权限。这种机制在某些情况下非常有用,比如当想要将类的私有成员仅对特定的外部类开放时。令人惊讶的是,这种方式实际上增强了封装性。如果使用公有成员函数,那么所有客户端都将能够访问它,包括那些不希望访问它的客户端。
以下是一个典型的案例。希望所有客户端都能够获取数据,但只有tDataCreator
能够创建/设置数据。
class tData {
friend class tDataCreator;
public:
tData(): m_data(-1) {}
int get() {
return m_data;
}
private:
void set(int data) {
m_data = data;
}
int m_data;
};
然而,友元类并非没有代价。在这种情况下,如果有更多私有成员,无法控制友元类被允许访问的成员,除非采取其他措施。
今天,当在审查团队中一位程序员的代码时,看到了友元类的使用。坦白说,不记得上次看到它是什么时候了。脑海中的一个声音告诉,可能会出问题。以下是他的代码。
背景:这是一个实际代码的高度简化版本,以便直接切入主题。这里简要介绍一下背景。链路层发现协议(LLDP)是由IEEE定义的802.1AB层2协议。它被交换机和终端站用来通过定期交换称为LLDPDU的LLDP帧来发现它们的邻居。设备的信息存储在其本地MIB数据库中。邻居的信息保存在其远程MIB数据库中。可以使用像SNMP这样的管理协议来拉取这些信息。就是这样!正如所能想象的,一个简单的深度优先搜索(DFS)就足够了。
为了进一步简化事情,假设网络中没有物理环路。设备远程MIB中的信息也不会形成环路。在现实中,这不是真的。两个设备之间只有一个物理链路的生态系统也有一个“虚拟”环路,因为它们中的每一个都出现在对方的远程MIB中。
class tTopoNode {
friend class tTopology;
public:
tTopoNode(const string &IP): m_IP(IP) {}
private:
void addNeighbor(std::shared_ptr node) {
m_neighbors.push_back(node);
}
typedef std::vector> tNeighbors;
string m_IP;
tNeighbors m_neighbors;
};
类tTopology
负责构建网络拓扑结构。
shared_ptr tTopology::buildTopology(const string &IP) {
shared_ptr node(new tTopoNode(IP));
tLLDPRemoteMIB remote = readLLDPRemoteMIB(IP);
for (tLLDPRemoteMIB::const_iterator it = remote.begin(); it != remote.end(); ++it) {
shared_ptr neighbor = buildTopology(*it);
node->addNeighbor(neighbor);
}
return node;
}
这种设计似乎很合理,对吗?这是一个有效的设计,直到再次思考:为什么需要这个友元类呢?需要它,因为tTopology
是唯一被允许向tTopoNode
添加邻居的类。之后,tTopoNode
对其他人来说是只读的。但为什么需要addNeighbor()
呢?不能在构造时就设置所有邻居吗?如果所有邻居在当前节点创建之前就已经创建好了,这是可以实现的。这是否让想起了什么?
是的!需要的是后序遍历。如果在当前节点创建之前完成所有邻居的创建,问题就解决了。让尝试一下。以下是更改后的部分。
class tTopoNode {
public:
tTopoNode(const string &IP, const tNeighbors &neighbors): m_IP(IP), m_neighbors(neighbors) {}
private:
string m_IP;
tNeighbors m_neighbors;
};
shared_ptr tTopology::buildTopology(const string &IP) {
tLLDPRemoteMIB remote = readLLDPRemoteMIB(IP);
tNeighbors neighbors;
for (tLLDPRemoteMIB::const_iterator it = remote.begin(); it != remote.end(); ++it) {
neighbors.push_back(buildTopology(*it));
}
return shared_ptr(new tTopoNode(IP, neighbors));
}