一个友元类使用误区(C++)

时间:2022-06-01 20:20:13

这个问题,我困扰了好一会,决定记录一下。

一、问题引出以及分析

问题代码简化如下:

class B;
class A
{
friend class B;
private:
A() = default;
bool operator()(int lhs,int rhs){ return lhs<rhs; }
};

class B
{
//...working
add_item(int times){ pq.push(times); }
private:
std::priority_queue<int,std::vector<int> ,A> pq;
};
编译出错提示信息:

 'bool A::operator()(int, int)' is private

你知道问题出现在哪里吗??如果你看出来了,那么恭喜你,我是花了挺长时间纠结的。


我的理解是这样的。 类A有一个删除的默认构造函数,类的用户是没有权限新建实例的,主要是为了封装数据。我一开始以为,B是A的友元类,理论上在B的作用域中可以建立A的对象,访问A的私有接口。 这样想确实没有错误,问题出现在我是在类B的成员std::priority_queue中使用类A。 友元不具有传递性和继承性,只能在B类的作用域中可以使用,相当于在B的成员函数中使用。但是std::priority_queue中的成员不具备访问类A的权限。除非将std::priority_queue声明为类A的友元类。因此定义如下函数便报错。

 add_item(int times){ pq.push(times); }
因为在pq的成员中使用了类A的成员函数, 每次向pq插入一个元素便会调用这个类A这个成员,如下

bool operator()(int lhs,int rhs){ return  lhs<rhs; }


二、问题解决

1、很自然会想到将类A的成员权限设置为public。 这样所有类A的用户都能访问,确实能解决问题。但是这样不能对数据很好的封装。因为类A只是一个辅助类。
2、类A只是作为一个函数对象存在而已,有没有别的办法代替类A?  答案是有,使用其他函数对象(函数,函数指针,lambda表达式,重载了函数调用运算符的类),这里lambda表达式代替A, 但是这里有一个小问题。lambda是一个函数对象实例而不是一种类型(type),
而std::priority_queue接收的是需要一个类型。 这需要一个转化。

template<
class T,
class Container = std::vector<T>,
class Compare = std::less<typename Container::value_type>
> class priority_queue;

可能会写出如下代码:

class B
{
//...working
add_item(int times){ pq.push(times); }
private:
auto Less = [](int& lhs,int rhs)->bool {return lhs<rhs;}
std::priority_queue<int,std::vector<int> ,decltype(Less)> pq;
};
这也是行不通的,类在编译的时候只看声明,auto需要根据给出的lambda 表达式推断出对应的类型,因此成员Less出错,当然pq声明也就出错了。


3、接下来就开始思考如何应对这个问题。查看std::priority_queue的constructor. 自定义一个函数比较器类型。

explicit priority_queue( const Compare& compare = Compare(),
const Container& cont = Container() );(until C++11)
priority_queue( const Compare& compare, const Container& cont );(since C++11)
//...省略

代码如下: 使用简单函数指针作为类型。新建一个对象需要提供这个函数指针类型的一个实例。

#include <iostream>
#include<stdio.h>
#include<queue>
#include<vector>
#include<functional>
class B
{
    //...working
    add_item(int times){ pq.push(times); }
private:
    std::priority_queue<int,std::vector<int> ,auto(*)(const int& ,const int&)->bool >
    pq{
        [](const int& lhs, const int& rhs)->bool
        {
            return lhs < rhs;
        }
    };
};
在C++11。 可以使用标准库提供的函数类型(std::function)

class B
{
//...working
add_item(int times){ pq.push(times); }
private:
std::priority_queue<int,std::vector<int> ,std::function<bool(const int&,const int& )> >
pq{
[](const int& lhs, const int& rhs)->bool
{
return lhs < rhs;
}
};
};


这个只是记录,从遇到问题到最后解决。实现了自己想到的结果。这当中难免有错误,欢迎指出来, 有好的想法欢迎提出来。