如何允许全局函数访问私有成员?
限制是您不允许friend
在类声明中直接使用全局函数。原因是我不希望用户必须在头文件中看到所有这些全局函数。函数本身是在实现文件中定义的,我想尽可能地将它们隐藏在那里。
现在你可能想知道为什么我有这么多这些全局函数。为了简单起见,我在窗口中注册了各种 WNDPROC 函数作为回调,它们必须是全局的。此外,他们必须能够更新对各种类来说是私有的信息。
我想出了 2 个解决方案,但都有些棘手。
解决方案 1. 让所有需要后门的成员,protected
而不是private
. 在实现文件中,声明一个从原始类继承但为受保护成员提供公共 getter 的类更改器。当您需要受保护的成员时,您可以简单地转换为 changer 类:
//Device.h
class Device{
protected:
std::map<int,int> somethingPrivate;
};
//Device.cpp
DeviceChanger : public Device{
private:
DeviceChanger(){} //these are not allowed to actually be constructed
public:
inline std::map<int,int>& getMap(){ return somethingPrivate; }
};
void foo(Device* pDevice){ ((DeviceChanger*)pDevice)->getMap(); }
当然,继承这个类的用户现在可以访问受保护的变量,但它至少允许我隐藏大部分重要的私有变量,因为它们可以保持私有。
这是可行的,因为DeviceChanger
实例具有与 完全相同的内存结构Device
,因此没有任何段错误。当然,这正在蔓延到未定义的 C++ 域,因为该假设依赖于编译器,但我关心的所有编译器(MSVC 和 GCC)都不会改变每个实例的内存占用,除非添加了新的成员变量。
解决方案2. 在头文件中,声明一个friend changer类。在实现文件中,定义友元类并使用它通过静态函数获取私有成员。
//Device.h
class DeviceChanger;
class Device{
friend DeviceChanger;
private:
std::map<int,int> somethingPrivate;
};
//Device.cpp
class DeviceChanger{
public:
static inline std::map<int,int>& getMap(Device* pDevice){ return pDevice->somethingPrivate; }
};
void foo(Device* pDevice){ DeviceChanger::getMap(pDevice); }
虽然这确实为我的所有课程添加了一个朋友(这很烦人),但只有一个朋友可以将信息转发给任何需要它的全局函数。当然,用户现在可以简单地定义自己的DeviceChanger
类,然后自己自由更改任何私有变量。
有没有更可接受的方式来实现我想要的?我意识到我正试图绕过 C++ 类保护,但我真的不想让每个需要访问其私有成员的类中的每个全局函数都成为朋友。它在头文件中很丑陋,并且不容易添加/删除更多功能。
编辑: 混合使用 Lake 和 Joel 的答案,我想出了一个完全符合我要求的想法,但是它使实现变得非常肮脏。基本上,您定义了一个具有各种公共/私有接口的类,但它的实际数据存储为指向结构的指针。该结构在 cpp 文件中定义,因此它的所有成员对该 cpp 文件中的任何内容都是公共的。即使用户定义了自己的版本,也只会使用实现文件中的版本。
//Device.h
struct _DeviceData;
class Device {
private:
_DeviceData* dd;
public:
//there are ways around needing this function, however including
//this makes the example far more simple.
//Users can't do anything with this because they don't know what a _DeviceData is.
_DeviceData& _getdd(){ return *dd; }
void api();
};
//Device.cpp
struct _DeviceData* { bool member; };
void foo(Device* pDevice){ pDevice->_getdd().member = true; }
这基本上意味着Device
除了指向某个数据块的指针之外,每个实例都是完全空的,但它为访问用户可以使用的数据提供了一个接口。当然,接口完全是在cpp文件中实现的。
此外,这使得数据非常私密,甚至用户都看不到成员名称和类型,但您仍然可以在实现文件中自由使用它们。最后,您可以继承Device
并获取所有功能,因为实现文件中的构造函数将创建 a_DeviceData
并将其分配给指针,从而为您提供所有api()
功能。您确实必须更加小心移动/复制操作以及内存泄漏。
莱克给了我这个想法的基础,所以我给了他信任。谢谢你,先生!