如何允许全局功能访问私有成员?
限制是您不允许直接friend在类声明中使用全局函数.原因是因为我不希望用户必须在头文件中看到所有这些全局函数.函数本身在实现文件中定义,我希望尽可能地将它们隐藏在那里.
现在你可能想知道为什么我有这么多全局函数.为了简单起见,我正在使用windows注册各种WNDPROC函数作为回调,它们必须是全局的.此外,他们必须能够更新各种类别私有的信息.
我想出了两个解决方案,但两者都有点粘.
解决方案1.让所有需要后门的成员protected而不是private.在实现文件中,声明一个类转换器,它继承自原始类,但为受保护的成员提供公共getter.当您需要受保护的成员时,您可以简单地转换为更改者类:
//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(); }
Run Code Online (Sandbox Code Playgroud)
当然,继承此类的用户现在可以访问受保护的变量,但它允许我至少隐藏大多数重要的私有变量,因为它们可以保持私有.
这是有效的,因为DeviceChanger实例具有完全相同的内存结构Device,因此没有任何段错误.当然,这正在进入未定义的C++域,因为该假设依赖于编译器,但我关注的所有编译器(MSVC和GCC)都不会更改每个实例的内存占用,除非添加了新的成员变量.
解决方案2.在头文件中,声明一个朋友转换器类.在实现文件中,定义该友元类并使用它通过静态函数获取私有成员.
//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); }
Run Code Online (Sandbox Code Playgroud)
虽然这确实为我的所有类添加了一个朋友(这很烦人),但只有一个朋友可以将信息转发给任何需要它的全局函数.当然,用户可以简单地定义自己的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; }
Run Code Online (Sandbox Code Playgroud)
这基本上意味着Device除了指向某个数据块的指针之外,每个实例都是完全空的,但它在访问用户可以使用的数据时设置了一个接口.当然,接口完全在cpp文件中实现.
此外,这使得数据非常私密,甚至用户也无法看到成员名称和类型,但您仍然可以在实现文件中自由使用它们.最后,您可以继承Device并获取所有功能,因为实现文件中的构造函数将创建一个_DeviceData并将其分配给指针,从而为您提供所有api()功能.您必须更加小心移动/复制操作以及内存泄漏.
湖给了我这个想法的基础,所以我给他信任.谢谢你,先生!
我通常通过以抽象类的形式提取应用程序程序员接口来解决这个问题,抽象类是应用程序程序员(即库的用户)能够使用的类型和操作的集合。
然后,在我的实现中,我声明公开将由其他类在我的包中使用的所有方法和类型。
例如:
我以类似于以下的方式定义 API 类:
class IDevice {
public:
// What the api user can do with the device
virtual void useMe() = 0;
};
Run Code Online (Sandbox Code Playgroud)
然后,在我的库中(未暴露给用户界面):
class Device : public IDevice {
public:
void useMe(); // Implementation
void hiddenToUser(); // Method to use from other classes, but hidden to the user
}
Run Code Online (Sandbox Code Playgroud)
然后,对于作为 API 一部分的每个标头(接口),我将使用 IDevice 类型而不是 Device 类型,并且当在内部我必须使用 Device 类时,我只需将指针向下转换为 Device。
假设您需要一个使用 Device 类的 Screen 类,但对用户完全隐藏(因此不会有任何要实现的 API 抽象类):
#include "Device.h"
class Screen {
void doSomethingWithADevice( Device* device );
}
// Screen.cpp
void Screen::doSomethingWithADevice( Device* device ){
device->hiddenToUser();
}
Run Code Online (Sandbox Code Playgroud)
这样,您就不必仅仅因为您不希望用户看到/使用某些内容而将其设为私有。您获得了进一步的抽象层(公共之上的 1 层),我将其称为 API。你将会拥有:
因此,您可以声明需要注册为回调方法的公共方法,而用户不会看到它们。
最后,我将 API 的内容与二进制文件一起交付给用户,以便用户可以准确访问我在 API 中明确定义的内容,而无需访问其他内容。