如何允许全局功能访问私有成员?
约束条件是不允许在类声明中直接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(); }
当然,继承此类的用户现在可以访问受保护的变量,但它允许我至少隐藏大多数重要的私有变量,因为它们可以保持私有。
这是有效的,因为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); }
虽然这确实为我的所有类添加了一个朋友(这很烦人),但只有一个朋友可以将信息转发给任何需要它的全局函数。当然,用户可以简单地定义自己的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
并获取所有功能,因为实现文件中的构造函数将创建_DeviceData
并将其分配给指针,这将为您提供所有api()
功率。你必须更加小心移动/复制操作,以及内存泄漏。
Lake给了我这个想法的基础,所以我给他信任。谢谢先生!
答案 0 :(得分:3)
我通常通过以抽象类的形式提取应用程序编程接口来解决这个问题,这是应用程序员的类型和操作集(即您图书馆的用户)将能够使用。
然后,在我的实现中,我声明了 public 所有将在我的包中被其他类使用的方法和类型。
例如:
我以类似于:
的方式定义API类class IDevice {
public:
// What the api user can do with the device
virtual void useMe() = 0;
};
然后,在我的库中(未暴露给用户界面):
class Device : public IDevice {
public:
void useMe(); // Implementation
void hiddenToUser(); // Method to use from other classes, but hidden to the user
}
然后,对于作为API一部分的每个头(接口),我将使用IDevice类型而不是Device类型,并且当在内部我将不得不使用Device类时,我将仅将指针强制转换为设备
假设你需要一个使用类Device的Screen类,但是对用户来说是完全隐藏的(因此不会有任何API抽象类来实现):
#include "Device.h"
class Screen {
void doSomethingWithADevice( Device* device );
}
// Screen.cpp
void Screen::doSomethingWithADevice( Device* device ){
device->hiddenToUser();
}
这样,您不必因为不希望用户查看/使用它而私有化。你获得了另一个抽象层(公开上面1个),我称之为API。你将拥有:
因此,您可以声明需要注册为回调方法的公共方法,而无需用户看到它们。
最后,我将API的内容与二进制文件一起提供给用户,这样用户就可以完全访问我在API中明确定义的内容,而不是其他内容。
答案 1 :(得分:1)
您可能会问一个特定的编码问题,但我想退后一步,检查您为什么要这样做,以及解决方案。
您是否根据私人州做出决定?
class Kettle {
private:
int temperatureC;
public:
void SwitchOff();
};
void SwitchOffKettleIfBoiling(Kettle& k) {
if (k.temperatureC > 100) { // need to examine Kettle private state
k.SwitchOff();
}
}
这是相对糟糕的,因为Kettle
的抽象现在泄漏到SwitchOffKettleIfBoiling
函数外部,以耦合到私有temperatureC
的形式。这有点好:
class Kettle {
private:
int temperatureC;
public:
void SwitchOffIfBoiling() {
if (temperatureC > 100) {
SwitchOff();
}
}
};
void SwitchOffKettleIfBoiling(Kettle& k) {
k.SwitchOffIfBoiling();
}
这种做法称为Tell, don't Ask。
有时您的数据明显相关但在不同的角色中使用。看看这个例子:
class Car {
private:
int statusFactor;
public:
void Drive();
};
void DriveSomewhere(Car& c) {
c.Drive();
// ...
}
void ShowOffSomething(const Car &c) {
// How can we access statusFactor, without also exposing it to DriveSomewhere?
}
解决这个问题的一种方法是使用代表这些责任的接口。
class IVehicle {
public:
virtual void Drive() = 0;
};
class IStatusSymbol {
public:
virtual int GetStatusFactor() const = 0;
};
class Car : public IVehicle, public IStatusSymbol {
// ...
};
void DriveSomewhere(IVehicle& v) {
v.Drive();
// ...
}
void ShowOffSomething(const IStatusSymbol &s) {
int status = s.GetStatusFactor();
// ...
}
此模式称为Facade pattern。它有助于在不限制实现的情况下保持良好的抽象。
答案 2 :(得分:1)
这是pimpl的一个(非常)粗略的例子。
//Device.h
class DeviceImpl;
class Device {
public:
Device();
private:
std::unique_ptr<DeviceImpl> pimpl;
};
//Device.cpp
class DeviceImpl {
public:
friend LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
private:
std::map<int,int> somethingPrivate;
};
Device::Device()
: pimpl(new DeviceImpl)
{
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
DeviceImpl* pimpl = reinterpret_cast<DeviceImpl*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
use(pimpl->somethingPrivate);
// omitting the SetWindowLongPtr that you have to do before calling GetWindowLongPtr,
// but the concept is the same - you'd probably do it in WM_CREATE
}
答案 3 :(得分:1)
现在你可能想知道为什么我有这么多全球性的 功能。为了简单起见,我正在注册各种WNDPROC 以windows作为回调函数,它们必须是全局的。 此外,他们必须能够更新其他信息 私人参加各种课程。
您可以使用静态成员函数来执行此操作而不是全局函数。那么你可以很好地得到私人会员。代码看起来有点像这样。
class MyClass {
private:
std::string some_data;
static void onEvent( void * user_data );
};
void MyClass::onEvent( void * user_data ) {
MyClass* obj = (MyClass*)(user_data);
std::cout<<some_data<<std::endl;
};
...
register_callback( &MyClass::onEvent, &myClassInstance);
唯一的问题是公开onEvent
函数名称。解决方案是提取一个接口,以便不暴露任何私有数据或函数(因为IMO泄露私有实现与泄露私有函数的名称一样糟糕。)
// Header File.
class IMyClass {
//...
// public stuff goes here
//...
};
// Implementation file.
class MyClass : public IMyClass {
private:
std::string some_data;
static void onEvent( void * user_data );
};
void MyClass::onEvent( void * user_data ) {
MyClass* obj = (MyClass*)(user_data);
std::cout<<some_data<<std::endl;
};
...
register_callback( &MyClass::onEvent, &myClassInstance);
编辑:基于对其他答案的一些回答,看起来可行的解决方案看起来更像是这样。
// IUSBDeviceBackend.h (private)
class IUSBDeviceBackend {
public:
virtual void update(USBUpdateData data)=0;
virtual bool resondsTo(USBUpdateCode code)=0
virtual ~IUSBDeviveBackend() {}
};
// IUSBDeviceUI.h (public)
class IUSBDeviceUI {
public:
virtual void showit()=0;
};
// MyDevice.h & MyDevice.cpp (both private)
class MyDevice : public IUSBDeviceBackend, public IUSBDeviceUI {
void update(USBUpdateData data) { dataMap[data.key]=data.value; }
bool resondsTo(USBUpdateCode code) { return code==7; }
void showit(){ ... }
};
// main.cpp
main() {
std::vector<IUSBDeviceBackedn*> registry;
MyDevice dev;
registry.push_back(this);
set_user_data(®istry);
// ...
}
void mycallback(void* user_daya) {
std::vector<IUSBDeviceBackedn>* devices = reinterpret_cast<std::vector<IUSBDeviceBackedn>*>(user_data);
for(unsigned int i=0; i<devices->size(); ++i) {
if( (*devices)[i]->resondsTo( data.code ) ) { (*devices)[i]->update(data); }
}
}
答案 4 :(得分:0)
为什么不使用工厂方法将接口返回到内部类,但仍然允许全局变量访问这些内部类?例如:
// IDriver.h public interface:
class IDriver {
public:
virtual int getFoo() = 0;
// ... other public interface methods.
// The implementation of this method will contain code to return a Driver:
static IDriver* getDriver();
};
// Driver.h internal interface (available to WNDPROC functions):
class Driver : public IDriver {
public:
int getFoo(); // Must provide this in the real Driver.
void setFoo(int aFoo); // Provide internal methods that are not in the public interface,
// but still available to your WNDPROC functions
}
// In Driver.cc
IDriver* IDriver::getDriver() { return new Driver(); }
使用这种方法,IDriver.h将是一个众所周知的公共标题,但您只能在自己的代码中内部使用Driver.h。这种方法是众所周知的,并使用我的许多现有C +库(例如Java的JNI)来允许访问类的本机低级位,而不会将其暴露给用户。