我在库中有多个类,我们希望从客户端代码中隐藏内部。从客户端的角度来看,每个类都是从库类中查询的,只能使用作为一个不透明的指针。一个例子如下:
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
和doSomethingElse
的全局方法,如果另一种类型也定义doSomething
,则依赖于函数重载。但是,在这种情况下,很难找到所有"成员" IDE中的SomeSystem。
我很想实际使用成员函数:
struct SomeSystem {
void doSomething(Parameters params);
void doSomethingElse(Parameters params);
};
使用代码:
SomeSystem* system = lib->getSomeSystem();
system->doSomething(params);
system->doSomethingElse(params);
最后一段代码看起来不错,但SomeSystem不再是一个不透明的指针 - 它实际上定义了成员。我对此有点警惕。一个潜在的问题是一个定义规则。然而,"公众"定义和"私人"只有不同的翻译单元才能看到该类的定义。这里隐藏着其他任何不好的东西吗?如果客户端代码尝试在堆栈上实例化SomeSystem或使用new,则显然会使程序崩溃。但我愿意接受这一点。也许我可以通过在公共接口中提供私有构造函数来解决这个问题。
另一种方法当然是使用纯虚方法定义抽象类。 但是,如果不是绝对必要,我希望避免这种情况的开销。
修改
要明确我想知道,如果客户端包含一个不同的类定义(缺少某些成员)而不是实现使用的公共头是否合法,因为客户端从不实例化该类。 / p>
公共标题:
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) {
...
}
这在我测试时有效,但我不确定它是否以某种方式违反了标准。这两个标题从不包含在同一个翻译单元中。
答案 0 :(得分: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是理想的。与您已经拥有的相比,您也不一定需要分配或任何额外的开销。
如果您有系统*和函数声明(根据您的示例),编译器必须执行间接寻址。你需要跳转到该函数(因为它在另一个翻译单元中定义)和间接访问函数内的系统(因为它被当作一个指针)。
您真正需要做的就是为类定义一个接口,如下所示:
// 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-&gt; 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(这与你在上面提到的类似)你的例子。)