C++ 如何允许全局函数访问私有成员

C++ 如何允许全局函数访问私有成员,c++,C++,如何允许全局函数访问私有成员 约束条件是不允许在类声明中直接friend全局函数。原因是我不希望用户必须在头文件中看到所有这些全局函数。函数本身是在实现文件中定义的,我希望尽可能将它们隐藏在那里 现在你可能想知道为什么我有这么多全局函数。为了简单起见,我在windows中注册了各种WNDPROC函数作为回调,它们必须是全局的。此外,它们必须能够更新其他类专用的信息 我提出了两种解决方案,但都有点棘手 解决方案1。使所有需要后门的成员受到保护而不是私有。在实现文件中,声明一个从原始类继承但向受保护

如何允许全局函数访问私有成员

约束条件是不允许在类声明中直接
friend
全局函数。原因是我不希望用户必须在头文件中看到所有这些全局函数。函数本身是在实现文件中定义的,我希望尽可能将它们隐藏在那里

现在你可能想知道为什么我有这么多全局函数。为了简单起见,我在windows中注册了各种WNDPROC函数作为回调,它们必须是全局的。此外,它们必须能够更新其他类专用的信息

我提出了两种解决方案,但都有点棘手

解决方案1。使所有需要后门的成员
受到保护
而不是
私有
。在实现文件中,声明一个从原始类继承但向受保护成员提供公共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
类并自由更改任何私有变量

有没有一种更被接受的方式来实现我的目标?我意识到我在试图绕过C++类保护,但我真的不想在每个需要访问它的私有成员的类中都共享所有的全局函数;它在头文件中很难看,并且不容易添加/删除更多函数

//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); }
// 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);
编辑:我混合使用了莱克和乔尔的答案,想出了一个完全符合我要求的想法,但是它使实现变得非常肮脏。基本上,您定义了一个具有各种公共/私有接口的类,但它的实际数据存储为指向结构的指针。结构是在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; }
这基本上意味着
设备
的每个实例除了指向某个数据块的指针外都是完全空的,但它为访问用户可以使用的数据提供了一个接口。当然,接口完全在cpp文件中实现

此外,这使得数据非常私有,甚至用户都看不到成员名称和类型,但您仍然可以在实现文件中自由使用它们。最后,您可以从
设备
继承并获得所有功能,因为实现文件中的构造函数将创建一个
\u DeviceData
并将其分配给指针,这将为您提供所有
api()
功能。不过,对于移动/复制操作以及内存泄漏,您必须更加小心


莱克给了我这个想法的基础,所以我相信他。谢谢你,先生

您可能会问一个特定的编码问题,但我想退一步,看看您为什么要这样做,以及解决方案

打破抽象 你是根据私人国家做出决定的吗

class Kettle {
private:
    int temperatureC;
public:
    void SwitchOff();
};

void SwitchOffKettleIfBoiling(Kettle& k) {
    if (k.temperatureC > 100) { // need to examine Kettle private state
        k.SwitchOff();
    }
}
这是比较糟糕的,因为
cattle
的抽象现在以耦合到私有
temperatureC
的形式泄漏到
switchOffKettleifbounding
函数的外部。这有点好:

class Kettle {
private:
    int temperatureC;
public:
    void SwitchOffIfBoiling() {
        if (temperatureC > 100) {
            SwitchOff();
        }
    }
};

void SwitchOffKettleIfBoiling(Kettle& k) {
    k.SwitchOffIfBoiling();
}
这种做法被称为

多重责任 有时,您有明确相关但用于不同角色的数据。看看这个例子:

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();
    // ...
}
这种模式被称为。它有助于在不限制实现的情况下保持良好的抽象

 //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
}
//Device.h
类DeviceImpl;
类设备{
公众:
装置();
私人:
std::唯一的ptr pimpl;
};
//Device.cpp
类DeviceImpl{
公众:
友元LRESULT回调WndProc(HWND、UINT、WPARAM、LPARAM);
私人:
映射一些私有的东西;
};
设备::设备()
:pimpl(新设备MPL)
{
}
LRESULT回调WndProc(HWND HWND,UINT msg,WPARAM WPARAM,LPARAM LPARAM)
{
DeviceImpl*pimpl=reinterpret_cast(GetWindowLongPtr(hWnd,GWLP_USERDATA));
使用(pimpl->somethingPrivate);
//省略调用GetWindowLongPtr之前必须执行的SetWindowLongPtr,
//但概念是一样的——你可能会在WM_CREATE中这样做
}

我通常通过以抽象类的形式提取应用程序程序员接口来解决这个问题,抽象类是应用程序程序员(即库的用户)能够使用的类型和操作集

然后,在我的实现中,我声明了其他类将在我的包中使用的所有方法和类型

例如:

  • API:IDevice.h
  • 内部:Device.h Device.cpp
我以类似以下方式定义API类:

class IDevice {
 public:
  // What the api user can do with the device
  virtual void useMe() = 0;
};
然后,在我的库中(不暴露于用户界面):

然后,对于作为API一部分的每个头(接口),我将使用IDevice类型而不是设备类型,当我在内部必须使用设备类时,我将只将指针向下转换到设备

假设您需要一个Screen类,该类使用类设备,但对用户完全隐藏(因此不会有任何API抽象类来实现):

这样,你就不必仅仅因为不想让用户看到/使用某个东西就将其设置为私有。您可以获得另一层抽象(公共之上1层),我称之为API。您将有:

#include "Device.h" class Screen { void doSomethingWithADevice( Device* device ); } // Screen.cpp void Screen::doSomethingWithADevice( Device* device ){ device->hiddenToUser(); }
// 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(); }
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);
// 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(&registry);
  // ...
}

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); }
  }
}