C++ Code Complete 关于封装的建议?
在《Code Complete》中的“良好封装”部分中,建议隐藏私有实现细节。在 C++ 中给出了一个例子。这个想法基本上是将接口与实现完全分离,即使在类级别也是如此。
class Employee {
public:
...
Employee( ... );
...
FullName GetName() const;
String GetAddress() const;
private:
EmployeeImplementation *m_implementation;
};
这真的是很好的时间利用方式吗?这不仅看起来效率低下(这会带来什么样的性能损失?),而且 Code Complete 的整个座右铭(“管理复杂性”)似乎已经被颠倒了——这难道不会增加复杂性吗?
In the section on "Good Encapsulation" in Code Complete, it is recommended to hide private implementation details. An example is given in C++. The idea is basically to completely separate the interface from the implementation, even in the class level.
class Employee {
public:
...
Employee( ... );
...
FullName GetName() const;
String GetAddress() const;
private:
EmployeeImplementation *m_implementation;
};
Is this really a good use of time? Not only does this seem inefficient (what kind of performance penalties would this give?), but the whole motto of Code Complete ("managing complexity") seems to have been reversed- does this not add complexity?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
PIMPL 习惯用法的另一个优点可能是维护 ABI。请参阅实践中的 Pimpl 惯用法。
类的大小保持不变。这意味着您可以更改内部实现,同时保持接口完整。
如果实现以编译形式(lib、dll、so 等)分发,那么在某些情况下,您可能可以只替换库,而不必重新编译使用该类的代码。因此,只要公共接口不改变,您就可以将代码解耦为一个完全独立的模块。
正如其他人所说,它还减少了编译时间,这在某些情况下可能是足够的理由。
Another advantage of the PIMPL idiom may be in maintaining the ABI. See The Pimpl Idiom in practice.
The size of the class remains constant. This means that you may change the internal implementation, while keeping the interface intact.
If the implementation is distributed in compiled form (lib, dll, so, etc.), then, under some conditions, you may be able to just replace the library without having to recompile the code that uses the class. Thus, you decouple the code as a completely stand-alone module, so long as the public interface doesn't change.
As others have stated, it also reduces compilation time, which can be reason enough in some cases.
好吧,它确实增加了封装性,因为您的头文件现在只包含公共成员和指向私有实现的单个指针。
由于额外的间接级别,它还会(稍微?)降低性能。
“减少编译时间”是这里的关键问题。
如果您(您的公司)是该类的唯一用户,那么我认为您没有任何理由使用这个习惯用法。您的性能会降低,并且您应该每天(或定期)重建源代码(应该注意类之间的依赖关系)。
这意味着如果您是该类的唯一使用者,则编译时间应该在很大程度上无关紧要。
如果您要分发库,那么情况就完全不同了。标头的更改意味着您拥有的任何客户端都需要重建其应用程序才能使用您的新版本,即使您所做的是更改类的私有部分。此处使用 pimpl 习惯用法意味着动态库的用户看不到更改。
Well, it does increase encapsulation since your header file now contains only public members and a single pointer to a private implementation.
It also (slightly?) lowers performance due to the extra level of indirection.
The "reduces compilation time" is the key issue here.
If you (your company) are the only users of the class then I don't think you have any reason to use this idiom. You get lower performance and you should have daily (or periodic) rebuilds of your source code anyway (which should be aware of dependencies between the classes).
This means that compilation time should be largely irrelevant if you are the only consumer of the class.
If you are distributing the library then the story is completely different. Changes in headers mean any clients you have will need to rebuild their applications to use your new version, even if what you did was change the private parts of the class. Using the pimpl idiom here would mean the change is invisible to the users of your dynamic library.
通过指针进行额外的间接访问可能会导致额外的缓存未命中并减慢程序速度。 AFAIK,这个习惯用法(PIMPL)最常被建议用来减少编译时间。假设您有一个
employee.h
标头,其中包含类中的所有字段,而不仅仅是一个指针。现在,每当您更改员工详细信息(例如添加或删除字段)时,包括employee.h
在内的每个文件都必须重新编译。如果您拥有的只是指向employee.cpp
中定义的实现类的单个指针,那么当您更改EmployeeImplementation
时,只需重新编译employee.cpp
代码>.现在,减少的编译时间值得付出额外的成本吗?只有你才能决定这一点。
An extra level of indirection through pointer can cause extra cache misses and slow down your program. AFAIK, this idiom (PIMPL) is most often suggested to reduce compilation time. Say you have an
employee.h
header having with all fields in the class, instead of just one pointer. Now, whenever you change employee details (e.g. add or remove field), EVERY file includingemployee.h
has to be recompiled. If all you have is a single pointer to an implementation class defined inemployee.cpp
, then ONLYemployee.cpp
has to be recompiled when you changeEmployeeImplementation
.Now, is the reduced compilation time worth the extra cost? Only you can decide that.
我认为 pimpl 习惯用法的主要优点(或至少其中之一)不是节省编译时间,而是允许组件之间的松散耦合,即打破依赖关系。
假设您提供了许多其他组件使用的基础结构库。然后,正如 @zvrba 所指出的,每次更改私有实现细节时,所有客户端都必须重新编译。这可能不是什么大问题,但在大型复杂的项目中,组件之间的集成可能是一项复杂的任务。使用 pimpl,如果您的库是动态的(dll、.so),那么您的客户端不需要执行任何操作。
I think that the main advantage (or at least on of them) of the pimpl idiom is not saving compilation time, but allowing loose-coupling, i.e. breaking dependencies, between components.
Suppose you provide an infrastructure library that many other components use. Then, as @zvrba indicated, every time you change your private implementation details all your clients must recompile. It could be not a big deal, but in large and complex projects an integration between components may be a complex task. With pimpl, if your library is dynamic (dll, .so) then no action is required by your clients.
这个习惯用法用于抽象不良标头,仅此而已。仅当定义类所需的类型涉及包含泄漏宏、需要很长时间编译等的标头时才使用它。除此之外,通常不认为这样做是正确的。由于您的实现无论如何都需要动态分配和引用语义,因此您也可以将其设为一个接口并提供一个在 cpp 文件中具有定义的
CreateForMyPlatform()
方法。至少您可以在该场景中使用智能指针。This idiom is used for abstracting over poor headers, and not much else. Only use it if the types required to define the class involve including headers which leak macros, take a long time to compile, etc. Apart from that, it's generally not considered the correct thing to do. Since your implementation requires dynamic allocation and reference semantics anyway, you may as well just make it an interface and offer a
CreateForMyPlatform()
method that has a definition in a cpp file. At least you can use smart pointers for that scenario.