0

我在一个库中有多个类,这些类具有我希望对客户端代码隐藏的内部结构。从客户端的角度来看,每个类都是从一个库类中查询出来的,并且只用作一个不透明的指针。一个例子如下:

struct SomeSystem;
void doSomethingToSomeSystem(SomeSystem* system, Parameters params);
void doSomethingElseToSomeSystem(SomeSystem* system, Parameters params);

在实现方面, SomeSystem 有多个调用者不可见的成员。这一切都很好,但我不太喜欢笨拙的用法语法:

SomeSystem* system = lib->getSomeSystem();
doSomethingToSomeSystem(system, params);
doSomethingElseToSomeSystem(system, params);

另一种方法是:

struct SomeSystem;
namespace somesystem {
    void doSomething(SomeSystem* system, Parameters params);
    void doSomethingElse(SomeSystem* system, Parameters params);
}

使用代码:

SomeSystem* system = lib->getSomeSystem();
somesystem::doSomething(system, params);
somesystem::doSomethingElse(system, params);

如果另一种类型也doSomething定义了. 但是,在这种情况下,很难在 IDE 中找到 SomeSystem 的所有“成员”。doSomethingElsedoSomething

我很想实际使用成员函数:

struct SomeSystem {
    void doSomething(Parameters params);
    void doSomethingElse(Parameters params);
};

使用代码:

SomeSystem* system = lib->getSomeSystem();
system->doSomething(params);
system->doSomethingElse(params);

最后一个片段对我来说看起来不错,但 SomeSystem 不再是一个不透明的指针——它实际上定义了成员。我对此有点警惕。一个潜在的问题是单一定义规则。但是,类的“公共”定义和“私有”定义将只对不同的翻译单元可见。这里是否还隐藏着其他不好的东西?如果客户端代码尝试在堆栈上实例化 SomeSystem 或使用 new 它显然会使程序崩溃。但我愿意接受。也许我可以通过在公共接口中提供一个私有构造函数来解决这个问题。

另一种方法当然是用纯虚方法定义一个抽象类。但是,如果不是绝对必要,我想避免这种开销。

编辑:

为了清楚起见,我想知道客户端包含的公共标头包含与实现使用的类的不同定义(缺少一些成员)是否合法,因为客户端从不实例化该类。

公共标头:

struct SomeSystem {
    void doSomething(Parameters params);
    void doSomethingElse(Parameters params);
};

私人标头:

struct SomeSystem {
    Member member;
    void doSomething(Parameters params);
    void doSomethingElse(Parameters params);
};

私有源(包括私有标头):

void SomeSystem::doSomething(Parameters params) {
    ...
}
void SomeSystem::doSomethingElse(Parameters params) {
    ...
}

这在我测试时有效,但我不确定它是否以某种方式违反标准。这两个标题永远不会包含在同一个翻译单元中。

4

1 回答 1

0

PIMPL 习惯用法在这种情况下可能是理想的,但它是每次访问的额外间接,所以就是这样。

如果您只是在使用一些语法糖,另一种选择可能是利用 ADL - 它至少会将系统名称保留在函数名称之外:

// publicly shared header file
namespace one_system
{
  struct system;
  typedef system* system_handle;
  void do_something(system_handle );
};

// private implementation
namespace one_system
{
  struct system {};
  void do_something( system_handle ) { cout << "one"; }
};


int main() {
  auto handle = /* SOMETHING TO GET THIS SYSTEM */;
  do_something(handle); //do_something found by ADL
  return 0;
}

编辑

我仍然认为 PIMPL 是理想的。与已有的相比,您也不一定需要分配或任何额外的开销。

如果您有一个 system* 和一个函数声明(根据您的示例),编译器已经必须执行间接操作。您需要跳转到该函数(因为它在另一个翻译单元中定义)和间接访问函数内的系统(因为它被视为指针)。

您真正需要做的就是为类定义一个接口,如下所示:

// put this in a namespace or name it according to the system
class interface
{
    system_handle m_system;

    public:
    interface( system_handle s ) : m_system( s ) {}
    interface() = delete;

    void do_something();
};

现在在另一个翻译单元中,do_something() 被定义为在系统上执行它的操作。lib->GetSystem() 可以返回接口的一个实例。该接口可以在头文件中完全声明,因为它只包含公共函数。该系统仍然是完全私有的(因为 lib 的用户不会拥有声明其内容的头文件)。

此外,用户可以轻松地复制界面。它不关心它的指针来自哪里,因此库可以根据需要传入静态地址。

我可以看到的一个缺点是成员变量需要具有访问器(并且有许多人会争辩说每个成员变量都应该是私有的并且无论如何都应该具有公共或受保护的访问器)。

另一个是接口的 *this 将被传递到 do_something 并且可能不需要。这也可以通过在头文件中定义 do_something 来解决:

void do_something() { do_something_to_system( m_system ); }

现在编译器甚至应该能够优化 *this ,因为 do_something 可以内联,并且编译器可以轻松地插入代码以将 m_system 加载到寄存器中并调用 do_something_to_system (这与您在示例中提到的类似) .

于 2015-03-31T16:57:14.270 回答