6

在 Code Complete 的“良好封装”部分,建议隐藏私有实现细节。C++ 中给出了一个示例。这个想法基本上是将接口与实现完全分离,即使在类级别也是如此。

class Employee {
public:
    ...
    Employee( ... );
    ...

    FullName GetName() const;
    String GetAddress() const;

private:
    EmployeeImplementation *m_implementation;
};

这真的是一种很好的时间利用方式吗?这不仅看起来效率低下(这会带来什么样的性能损失?),而且代码完整(“管理复杂性”)的整个座右铭似乎已经被颠倒了——这不会增加复杂性吗?

4

5 回答 5

3

PIMPL 习语的另一个优点可能是维护ABI。请参阅实践中的 The Pimpl Idiom

类的大小保持不变。这意味着您可以更改内部实现,同时保持接口不变。

如果实现是以编译形式(lib、dll、so 等)分发的,那么在某些情况下,您可以只替换库而无需重新编译使用该类的代码。因此,只要公共接口不变,您就可以将代码解耦为一个完全独立的模块。

正如其他人所说,它还减少了编译时间,这在某些情况下可能是足够的理由。

于 2011-06-12T11:22:15.527 回答
2

好吧,它确实增加了封装性,因为您的头文件现在只包含公共成员和指向私有实现的单个指针。

由于额外的间接级别,它还(稍微?)降低了性能。

“减少编译时间”是这里的关键问题。

如果您(您的公司)是该课程的唯一用户,那么我认为您没有任何理由使用这个成语。您会获得较低的性能,并且无论如何您都应该每天(或定期)重建源代码(这应该知道类之间的依赖关系)。

这意味着如果您是该类的唯一使用者,那么编译时间在很大程度上应该是无关紧要的。

如果您正在分发库,那么情况就完全不同了。标头的更改意味着您拥有的任何客户端都需要重新构建他们的应用程序才能使用您的新版本,即使您所做的是更改了类的私有部分。在这里使用 pimpl 成语意味着更改对您的动态库的用户是不可见的。

于 2011-06-12T09:25:50.263 回答
1

通过指针的额外间接级别可能会导致额外的缓存未命中并减慢程序的速度。AFAIK,这个习语(PIMPL)最常被建议用来减少编译时间。假设您有一个employee.h包含类中所有字段的标题,而不仅仅是一个指针。现在,每当您更改员工详细信息(例如添加或删除字段)时,employee.h都必须重新编译包括在内的每个文件。如果您只有一个指向 中定义的实现类的指针employee.cpp,那么只有employee.cpp在您更改时才需要重新编译EmployeeImplementation

现在,减少的编译时间值得付出额外的代价吗?只有你可以决定。

于 2011-06-12T06:09:20.410 回答
1

我认为 pimpl 习惯用法的主要优点(或至少其中一个)不是节省编译时间,而是允许松散耦合,即打破组件之间的依赖关系。

假设您提供了许多其他组件使用的基础架构库。然后,正如@zvrba 所指出的,每次更改私有实现细节时,所有客户端都必须重新编译。这可能没什么大不了的,但在大型和复杂的项目中,组件之间的集成可能是一项复杂的任务。使用 pimpl,如果您的库是动态的(dll、.so),那么您的客户不需要任何操作。

于 2011-06-12T08:59:21.110 回答
1

这个习语用于对糟糕的标题进行抽象,仅此而已。仅当定义类所需的类型涉及包含泄漏宏的标头、需要很长时间编译等时才使用它。除此之外,通常不认为这样做是正确的。由于您的实现无论如何都需要动态分配和引用语义,因此您不妨将其设为接口并提供CreateForMyPlatform()在 cpp 文件中具有定义的方法。至少您可以在该场景中使用智能指针。

于 2011-06-12T11:25:55.057 回答