【问题标题】:Interfaces, hiding concrete implementation details in C++接口,隐藏 C++ 中的具体实现细节
【发布时间】:2012-09-09 01:23:56
【问题描述】:

我有一个关于在 C++ 库中隐藏接口细节的问题。下面的例子说明了这个问题:

假设我们有一个名为 ISystem 的类,它公开了 Init、Run、Tick、Shutdown、GetXXXSubSystem 等方法。 其中 X 是各种接口的指针,例如:ISoundSystem、IInputSystem

我们还有 ISystem 的具体实现,例如: Win32System、OSXSystem

这些特定的实现使用 pimpl 习惯用法来隐藏内部 例如 Win32System 实例化 Win32RawInputSystem 作为输入系统管理员。

所有此类管理器都有自己的 Init、Run、Tick 和 Shutdown 方法 它们不是接口的一部分(仅是具体实现),它们由具体的系统实现运行和管理。

调用 GetXXXSubSystem 的用户在没有这些方法(Init 等)的情况下获取接口指针,但是 他仍然可以将他得到的指针指向具体实施 并触发他不应该访问的 Init Run Tick 等方法。

问题是,是否有可能以某种方式隐藏具体类?我试图制作这些方法 在具体实现中保护并模板化最终将成为朋友的类型上的类,但这似乎是被禁止的,现有的 hack 不适用于 VC11。

我能想到的唯一方法是从标题中传输具体的实现声明 进入 Win32System 类的 cpp 文件,但我看到这样做的巨大缺点(甚至不确定这是否可行),因为这样每个子系统 也必须是此 cpp 文件的一部分,这将成为可维护性的噩梦。

我正在考虑的另一个解决方案是使用工厂方法,例如

(RawInput.h) 
IInputSystem* CreateRawInputSystem();

(RawInput.cpp)
class RawInput : public IInputSystem {}; ...

并将类的定义移动到 cpp 文件,但是,我将如何从库的其他部分(即在 Win32System impl 中)访问此类型?

是否可以从其他 .cpp 文件中包含 .cpp 文件?

提前感谢您的任何提示。

【问题讨论】:

  • C++ 的内部“安全”功能(例如,private:/protected:)旨在防止意外,而不是故意滥用。作为一般规则,我建议您也这样做(主要是因为几乎任何相反的尝试都注定从一开始就绝对失败)。
  • 问题是我需要将具体实现的一些类功能暴露给我的库内部,而库用户不可能。也许我错过了一些让我实现这一目标的设计模式。
  • 没有。您正在寻找的是更接近操作系统而不是设计模式。使代码工作并明确用户不应该直接使用哪些部分。对于代码的内部,即使尝试更多也是浪费时间和精力。

标签: c++ oop design-patterns


【解决方案1】:

如果您在这里开发一个库,那么您可以简单地选择不导出您不想公开的具体类的头文件。您不能强制转换为您没有定义的类。

示例:
MyProjectFolder/Src/Export/ISystem.h

#ifndef ISYSTEM_H
#define ISYSTEM_H

#include "IInputSystem.h"

class ISystem
{
public:
    virtual ~ISystem() {};
    virtual void Run()=0;
    virtual IInputSystem* GetInputSystem()=0;
};

#endif

MyProjectFolder/Src/Export/IInputSystem.h

#ifndef IINPUTSYSTEM_H
#define IINPUTSYSTEM_H

class IInputSystem
{
public:
    virtual ~IInputSystem() {};
    virtual void Foo()=0;
    virtual void Bar()=0;
};

#endif

MyProjectFolder/Src/Export/Win32System.h

#ifndef WIN32SYSTEM_H
#define WIN32SYSTEM_H

#include "ISystem.h"

class Win32System : public ISystem
{
public:
    Win32System();
    virtual void Run();
    virtual IInputSystem* GetInputSystem();

private:
    struct impl;
    impl* m_pImpl;
};

#endif

MyProjectFolder/Src/Win32RawInputSystem.h

#ifndef WIN32RAWINPUTSYSTEM_H
#define WIN32RAWINPUTSYSTEM_H

#include "IInputSystem.h"

class Win32RawInputSystem : public IInputSystem
{
public:
    virtual void Foo();
    virtual void Bar();

    virtual void Run(); // you do not want to expose that function
};

#endif

MyProjectFolder/Src/Win32System.cpp

#include "Win32System.h"

#include "Win32RawInputSystem.h"

struct Win32System::impl
{
    Win32RawInputSystem inputSys;
};

Win32System::Win32System()
 : m_pImpl(new impl)
{
}

void Win32System::Run()
{ // run run run
}

IInputSystem* Win32System::GetInputSystem()
{
    return &m_pImpl->inputSys;
}

因此,在构建项目时,它的包含搜索路径不仅是 Src/,而且是 Src/Export/。在您的库项目中,您可以使用所有类,包括 Win32RawInputSystem。在部署你的库时,你只会放弃那些位于 Src/Export/ 文件夹中的头文件。客户仍然可以使用该库,但他们永远不能将IInputSystem* 转换为Win32RawInputSystem*,因为他们没有那个标头。因此,该库的用户可以在IInputSystem* 上调用Foo()Bar(),但他们永远无法调用Run()

【讨论】:

  • 太好了,这正是我所说的隐藏类型的意思。事实上,解决方案是如此简单,以至于我很惊讶我遇到了问题。坦率地说,当谈到不能将模板参数定义为朋友时,我“撒谎”了。这个解决方案(C++11 的一个特性)是在 VC11 中实现的,所以它工作正常,我搞砸了。语法是friend T,其中T是模板类型。
猜你喜欢
  • 2016-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多