PIMPL 习惯用法真的在实践中使用吗?
我正在阅读 Herb Sutter 的《Exceptional C++》一书,在那本书中我了解了 PIMPL 习惯用法。基本上,这个想法是为类
的私有
对象创建一个结构,并动态分配它们以减少编译时间(并且还隐藏以更好的方式进行私有实现)。
例如:
class X
{
private:
C c;
D d;
} ;
可以改为:
class X
{
private:
struct XImpl;
XImpl* pImpl;
};
并且,在 .cpp 文件中,定义:
struct X::XImpl
{
C c;
D d;
};
这看起来很有趣,但我以前从未见过这种做法,无论是在我工作过的公司中,还是在我工作过的开源项目中。看过源代码。所以,我想知道这种技术是否真的应用于实践。
我应该在任何地方使用它还是谨慎使用?这种技术是否建议用于嵌入式系统(性能非常重要)?
I am reading the book "Exceptional C++" by Herb Sutter, and in that book I have learned about the PIMPL idiom. Basically, the idea is to create a structure for the private
objects of a class
and dynamically allocate them to decrease the compilation time (and also hide the private implementations in a better manner).
For example:
class X
{
private:
C c;
D d;
} ;
could be changed to:
class X
{
private:
struct XImpl;
XImpl* pImpl;
};
and, in the .cpp file, the definition:
struct X::XImpl
{
C c;
D d;
};
This seems pretty interesting, but I have never seen this kind of approach before, neither in the companies I have worked, nor in open source projects that I've seen the source code. So, I am wondering whether this technique is really used in practice.
Should I use it everywhere, or with caution? And is this technique recommended to be used in embedded systems (where the performance is very important)?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
我想我应该添加一个答案,因为尽管有些作者暗示了这一点,但我认为这一点还不够明确。
PIMPL的主要目的是解决N*M问题。这个问题在其他文献中可能有其他名称,但这里是一个简短的总结。
你有某种继承层次结构,如果你要向你的层次结构添加一个新的子类,它将需要你实现 N 或 M 个新方法。
这只是一个近似的手动解释,因为我最近才意识到这一点,所以我自己承认我还不是这方面的专家。
对现有观点的讨论
但是,我在几年前遇到过这个问题以及类似的问题,并且我对给出的典型答案感到困惑。 (大概我几年前第一次了解 PIMPL,并发现了这个问题和其他类似的问题。)
考虑到上述“优点”,它们都不是特别引人注目的我认为使用 PIMPL 的原因。因此我从未使用过它,并且我的程序设计因此受到影响,因为我放弃了 PIMPL 的实用性以及它真正可以用来完成的任务。
请允许我对每一项进行评论以解释:
1.
二进制兼容性仅在编写库时才有意义。如果您正在编译最终的可执行程序,那么这没有关系,除非您正在使用其他人的(二进制)库。 (换句话说,您没有原始源代码。)
这意味着这种优势的范围和实用性有限。只有编写以专有形式提供的库的人才会对它感兴趣。
2.
我个人认为这在当今很少有编译时间至关重要的项目中没有任何意义。也许这对于 Google Chrome 的开发者来说很重要。可能显着增加开发时间的相关缺点可能会抵消这一优点。我可能是错的,但我发现这不太可能,特别是考虑到现代编译器和计算机的速度。
3.
我并没有立即看到 PIMPL 带来的优势。通过传送头文件和二进制目标文件可以实现相同的结果。如果我面前没有具体的例子,很难理解为什么 PIMPL 在这里是相关的。相关的“东西”是传送二进制目标文件,而不是原始源代码。
PIMPL 实际上是做什么的:
你必须原谅我的回答有些含糊。虽然我不是软件设计这一特定领域的完全专家,但我至少可以告诉您一些相关信息。这些信息大部分是从设计模式中重复的。作者将其称为“桥模式”,又名“句柄”,又名“主体”。
本书中给出了编写窗口管理器的示例。这里的关键点是窗口管理器可以实现不同类型的窗口以及不同类型的平台。
例如,可能有一个
以及
上面的列表与另一个答案中给出的类似,其中另一个用户描述了编写应该与不同类型一起工作的软件DVD 之类的硬件玩家。 (我完全忘记了这个例子是什么。)
与《设计模式》书中所写的相比,我在这里给出的示例略有不同。
要点是有两种不同类型的事物应该使用继承层次结构来实现,但是使用单个继承层次结构在这里是不够的。 (N*M 问题,复杂度就像每个项目符号列表中事物数量的平方一样,这对于开发人员来说是不可行的。)
因此,使用 PIMPL,可以分离出窗口的类型并提供一个指针到实现类的实例。
因此 PIMPL:
可能还有其他方法来实现这一点,例如使用多重继承,但这通常是一种更复杂和困难的方法,至少根据我的经验。
I thought I would add an answer because although some authors hinted at this, I didn't think the point was made clear enough.
The primary purpose of PIMPL is to solve the N*M problem. This problem may have other names in other literature, however a brief summary is this.
You have some kind of inhertiance hierachy where if you were to add a new subclass to your hierachy, it would require you to implement N or M new methods.
This is only an approximate hand-wavey explanation, because I only recently became aware of this and so I am by my own admission not yet an expert on this.
Discussion of existing points made
However I came across this question, and similar questions a number of years ago, and I was confused by the typical answers which are given. (Presumably I first learned about PIMPL some years ago and found this question and others similar to it.)
Taking into account the above "advantages", none of them are a particularly compelling reason to use PIMPL, in my opinion. Hence I have never used it, and my program designs suffered as a consequence because I discarded the utility of PIMPL and what it can really be used to accomplish.
Allow me to comment on each to explain:
1.
Binary compatiability is only of relevance when writing libraries. If you are compiling a final executable program, then this is of no relevance, unless you are using someone elses (binary) libraries. (In other words, you do not have the original source code.)
This means this advantage is of limited scope and utility. It is only of interest to people who write libraries which are shipped in proprietary form.
2.
I don't personally consider this to be of any relevance in the modern day when it is rare to be working on projects where the compile time is of critical importance. Maybe this is important to the developers of Google Chrome. The associated disadvantages which probably increase development time significantly probably more than offset this advantage. I might be wrong about this but I find it unlikely, especially given the speed of modern compilers and computers.
3.
I don't immediatly see the advantage that PIMPL brings here. The same result can be accomplished by shipping a header file and a binary object file. Without a concrete example in front of me it is difficult to see why PIMPL is relevant here. The relevant "thing" is shipping binary object files, rather than original source code.
What PIMPL actually does:
You will have to forgive my slightly hand-wavey answer. While I am not a complete expert in this particular area of software design, I can at least tell you something about it. This information is mostly repeated from Design Patterns. The authors call it "Bridge Pattern" aka Handle aka Body.
In this book, the example of writing a Window manager is given. The key point here is that a window manager can implement different types of windows as well as different types of platform.
For example, one may have a
as well as
The list above is analagous to that given in another answer where another user described writing software which should work with different kinds of hardware for something like a DVD player. (I forget exactly what the example was.)
I give slightly different examples here compared to what is written in the Design Patterns book.
The point being that there are two seperate types of things which should be implemented using an inheritance hierachy, however using a single inheritance hierachy does not suffice here. (N*M problem, the complexity scales like the square of the number of things in each bullet point list, which is not feasible for a developer to implement.)
Hence, using PIMPL, one seperates out the types of windows and provides a pointer to an instance of an implementation class.
So PIMPL:
There may be other ways to implement this, for example with multiple inheritance, but this is usually a more complicated and difficult approach, at least in my experience.
当然是用的。我在我的项目中,几乎在每堂课上都使用它。
使用 PIMPL 习惯用法的原因:
二进制兼容性
当您开发库时,您可以向
XImpl
添加/修改字段,而不会破坏与客户端的二进制兼容性(这将意味着崩溃!)。由于当您向Ximpl
类添加新字段时,X
类的二进制布局不会改变,因此在次要版本更新中向库添加新功能是安全的。当然,您还可以向
X
/XImpl
添加新的公共/私有非虚拟方法,而不会破坏二进制兼容性,但这与标准标头/实现技术相同。数据隐藏
如果您正在开发一个库,尤其是一个专有库,那么最好不要透露使用了哪些其他库/实现技术来实现您的库的公共接口。要么是因为知识产权问题,要么是因为您认为用户可能会对实现做出危险的假设,或者只是通过使用可怕的转换技巧来破坏封装。 PIMPL 解决/缓解了这个问题。
编译时间
编译时间减少了,因为当您向
XImpl
类添加/删除字段和/或方法时,只需重建X
的源(实现)文件(这映射到在标准技术中添加私有字段/方法)。实际上,这是一个常见的操作。使用标准标头/实现技术(无 PIMPL),当您向
X
添加新字段时,每个分配X
的客户端(无论是在堆栈上还是在堆上)需要重新编译,因为它必须调整分配的大小。好吧,每个不分配 X 的客户端也都需要重新编译,但这只是开销(客户端生成的代码将是相同的)。更重要的是,使用标准标头/实现分离,即使将私有方法
X::foo()
添加到X,也需要重新编译
和XClient1.cpp
Xh
已更改,即使XClient1.cpp
由于封装原因不可能调用此方法!如上所述,它是纯粹的开销,并且与现实生活中的 C++ 构建系统的工作方式有关。当然,当您只修改方法的实现时,不需要重新编译(因为您没有触及标头),但这与标准标头/实现技术相同。
这取决于你的目标有多强大。然而,这个问题的唯一答案是:衡量和评估你的所得和失去。另外,请考虑到,如果您不发布供客户在嵌入式系统中使用的库,则只有编译时间优势适用!
Of course it is used. I use it in my project, in almost every class.
Reasons for using the PIMPL idiom:
Binary compatibility
When you're developing a library, you can add/modify fields to
XImpl
without breaking the binary compatibility with your client (which would mean crashes!). Since the binary layout ofX
class doesn't change when you add new fields toXimpl
class, it is safe to add new functionality to the library in minor versions updates.Of course, you can also add new public/private non-virtual methods to
X
/XImpl
without breaking the binary compatibility, but that's on par with the standard header/implementation technique.Data hiding
If you're developing a library, especially a proprietary one, it might be desirable not to disclose what other libraries / implementation techniques were used to implement the public interface of your library. Either because of Intellectual Property issues, or because you believe that users might be tempted to take dangerous assumptions about the implementation or just break the encapsulation by using terrible casting tricks. PIMPL solves/mitigates that.
Compilation time
Compilation time is decreased, since only the source (implementation) file of
X
needs to be rebuilt when you add/remove fields and/or methods to theXImpl
class (which maps to adding private fields/methods in the standard technique). In practice, it's a common operation.With the standard header/implementation technique (without PIMPL), when you add a new field to
X
, every client that ever allocatesX
(either on stack, or on heap) needs to be recompiled, because it must adjust the size of the allocation. Well, every client that doesn't ever allocate X also need to be recompiled, but it's just overhead (the resulting code on the client side will be the same).What is more, with the standard header/implementation separation
XClient1.cpp
needs to be recompiled even when a private methodX::foo()
was added toX
andX.h
changed, even thoughXClient1.cpp
can't possibly call this method for encapsulation reasons! Like above, it's pure overhead and is related with how real-life C++ build systems work.Of course, recompilation is not needed when you just modify the implementation of the methods (because you don't touch the header), but that's on par with the standard header/implementation technique.
That depends on how powerful your target is. However the only answer to this question is: measure and evaluate what you gain and lose. Also, take into consideration that if you're not publishing a library meant to be used in embedded systems by your clients, only the compilation time advantage applies!
似乎很多库都使用它来保持 API 的稳定性,至少对于某些版本而言是这样。
但对于所有的东西来说,你不应该不小心地到处使用任何东西。使用前一定要三思。评估它给您带来了哪些优势,以及它们是否值得您付出的代价。
它可能给您带来的优势是:
这些对您来说可能是也可能不是真正的优势。就像我一样,我不在乎几分钟的重新编译时间。最终用户通常也不这样做,因为他们总是从头开始编译一次。
可能的缺点是(也在这里,取决于实现以及它们是否对您来说是真正的缺点):
因此请小心地为所有内容赋予一个值,并自己评估它。对我来说,几乎总是发现使用 PIMPL 惯用法并不值得付出努力。我个人只在一种情况下使用它(或至少是类似的东西):
用于 Linux
stat
调用的 C++ 包装器。这里,C 头文件中的结构体可能会有所不同,具体取决于#defines
设置的内容。由于我的包装器标头无法控制所有这些,因此我仅在.cxx
文件中使用#include
来避免这些问题。It seems that a lot of libraries out there use it to stay stable in their API, at least for some versions.
But as for all things, you should never use anything everywhere without caution. Always think before using it. Evaluate what advantages it gives you, and if they are worth the price you pay.
The advantages it may give you are:
Those may or may not be real advantages to you. Like for me, I don't care about a few minutes recompilation time. End users usually also don't, as they always compile it once and from the beginning.
Possible disadvantages are (also here, depending on the implementation and whether they are real disadvantages for you):
So carefully give everything a value, and evaluate it for yourself. For me, it almost always turns out that using the PIMPL idiom is not worth the effort. There is only one case where I personally use it (or at least something similar):
My C++ wrapper for the Linux
stat
call. Here the struct from the C header may be different, depending on what#defines
are set. And since my wrapper header can't control all of them, I only#include <sys/stat.h>
in my.cxx
file and avoid these problems.我同意所有其他人关于商品的看法,但让我提出一个限制的证据:与模板配合得不好。
原因是模板实例化需要实例化发生处可用的完整声明。 (这就是您看不到定义到 .cpp 文件中的模板方法的主要原因。)
您仍然可以引用模板化子类,但由于您必须全部包含它们,因此编译时“实现解耦”的每个优点(避免包含所有特定于平台的代码(缩短编译时间)都会丢失。
对于经典的OOP(基于继承)来说,这是一个很好的范例,但不适用于泛型编程(基于专业)。
I agree with all the others about the goods, but let me put in evidence about a limit: doesn't work well with templates.
The reason is that template instantiation requires the full declaration available where the instantiation took place. (And that's the main reason you don't see template methods defined into .cpp files.)
You can still refer to templatised subclasses, but since you have to include them all, every advantage of "implementation decoupling" on compiling (avoiding to include all platform-specific code everywhere, shortening compilation) is lost.
It is a good paradigm for classic OOP (inheritance based), but not for generic programming (specialization based).
其他人已经提供了技术上的优点/缺点,但我认为以下几点值得注意:
首先,不要教条。如果 PIMPL 适用于您的情况,请使用它 - 不要仅仅因为“它是更好的 OO,因为它确实隐藏了实现”等而使用它。引用 C++ 常见问题解答:
只是为了给您提供一个开源软件的使用示例以及原因:OpenThreads,OpenSceneGraph。主要思想是从标头(例如,
)中删除所有特定于平台的代码,因为内部状态变量(例如,线程句柄)因平台而异。这样,人们就可以针对您的库编译代码,而无需了解其他平台的特性,因为一切都是隐藏的。Other people have already provided the technical up/downsides, but I think the following is worth noting:
First and foremost, don't be dogmatic. If PIMPL works for your situation, use it - don't use it just because "it's better OO since it really hides implementation", etc. Quoting the C++ FAQ:
Just to give you an example of open source software where it is used and why: OpenThreads, the threading library used by the OpenSceneGraph. The main idea is to remove from the header (e.g.,
<Thread.h>
) all platform-specific code, because internal state variables (e.g., thread handles) differ from platform to platform. This way one can compile code against your library without any knowledge of the other platforms' idiosyncrasies, because everything is hidden.我主要考虑将 PIMPL 用于公开供其他模块用作 API 的类。这有很多好处,因为它使得重新编译 PIMPL 实现中所做的更改不会影响项目的其余部分。此外,对于 API 类,它们促进了二进制兼容性(模块实现中的更改不会影响这些模块的客户端,它们不必重新编译,因为新实现具有相同的二进制接口 - PIMPL 公开的接口)。
至于对每个类使用 PIMPL,我会谨慎考虑,因为所有这些好处都是有代价的:需要额外的间接级别才能访问实现方法。
I would mainly consider PIMPL for classes exposed to be used as an API by other modules. This has many benefits, as it makes recompilation of the changes made in the PIMPL implementation does not affect the rest of the project. Also, for API classes they promote a binary compatibility (changes in a module implementation do not affect clients of those modules, they don't have to be recompiled as the new implementation has the same binary interface - the interface exposed by the PIMPL).
As for using PIMPL for every class, I would consider caution because all those benefits come at a cost: an extra level of indirection is required in order to access the implementation methods.
我认为这是最基本的解耦工具之一。
我在嵌入式项目 (SetTopBox) 上使用 PIMPL(以及 Exceptional C++ 中的许多其他惯用语)。
在我们的项目中,这个习惯用法的特殊目的是隐藏 XImpl 类使用的类型。
具体来说,我们用它来隐藏不同硬件的实现细节,其中会引入不同的标头。我们为一个平台提供了不同的 XImpl 类实现,而为另一个平台提供了不同的实现。无论平台如何,X 类的布局都保持不变。
I think this is one of the most fundamental tools for decoupling.
I was using PIMPL (and many other idioms from Exceptional C++) on embedded project (SetTopBox).
The particular purpose of this idiom in our project was to hide the types XImpl class uses.
Specifically, we used it to hide details of implementations for different hardware, where different headers would be pulled in. We had different implementations of XImpl classes for one platform and different for the other. Layout of class X stayed the same regardless of the platform.
我过去经常使用这种技术,但后来发现自己正在放弃它。
当然,向类的用户隐藏实现细节是一个好主意。但是,您也可以通过让类的用户使用抽象接口并将实现细节作为具体类来实现这一点。
pImpl 的优点是:
假设这个接口只有一个实现,不使用抽象类/具体实现会更清晰
如果您有一套类(一个模块),其中多个类访问相同的“impl”,但该模块的用户只会使用“公开”的类。
如果这被认为是一件坏事,则没有 v-table。
我发现 pImpl 的缺点(抽象接口效果更好)
虽然您可能只有一个“生产”实现,但通过使用抽象接口,您还可以创建一个适用于单元测试的“模拟”实现。
(最大的问题)。在 unique_ptr 和移动之前,您对于如何存储 pImpl 的选择有限。原始指针和您的类不可复制的问题。旧的 auto_ptr 不能与前向声明的类一起使用(无论如何不是在所有编译器上)。因此,人们开始使用shared_ptr,这很好地使您的类可复制,但当然,两个副本都具有您可能意想不到的相同的底层shared_ptr(修改一个副本,然后两个副本都被修改)。因此,解决方案通常是对内部指针使用原始指针,并使该类不可复制,然后返回一个shared_ptr。所以两次调用 new。 (实际上,旧的shared_ptr的3个给了你第二个)。
从技术上讲,并不是真正的常量正确,因为常量不会传播到成员指针。
总的来说,多年来我已经从 pImpl 转向抽象接口的使用(以及创建实例的工厂方法)。
I used to use this technique a lot in the past but then found myself moving away from it.
Of course it is a good idea to hide the implementation detail away from the users of your class. However you can also do that by getting users of the class to use an abstract interface and for the implementation detail to be the concrete class.
The advantages of pImpl are:
Assuming there is just one implementation of this interface, it is clearer by not using abstract class / concrete implementation
If you have a suite of classes (a module) such that several classes access the same "impl" but users of the module will only use the "exposed" classes.
No v-table if this is assumed to be a bad thing.
The disadvantages I found of pImpl (where abstract interface works better)
Whilst you may have only one "production" implementation, by using an abstract interface you can also create a "mock" inmplementation that works in unit testing.
(The biggest issue). Before the days of unique_ptr and moving you had restricted choices as to how to store the pImpl. A raw pointer and you had issues about your class being non-copyable. An old auto_ptr wouldn't work with forwardly declared class (not on all compilers anyway). So people started using shared_ptr which was nice in making your class copyable but of course both copies had the same underlying shared_ptr which you might not expect (modify one and both are modified). So the solution was often to use raw pointer for the inner one and make the class non-copyable and return a shared_ptr to that instead. So two calls to new. (Actually 3 given old shared_ptr gave you a second one).
Technically not really const-correct as the constness isn't propagated through to a member pointer.
In general I have therefore moved away in the years from pImpl and into abstract interface usage instead (and factory methods to create instances).
这是我遇到的一个实际场景,这个习惯用法很有帮助。我最近决定在游戏中支持 DirectX 11 以及我现有的 DirectX 9 支持引擎。
该引擎已经封装了大部分DX功能,因此没有直接使用DX接口;它们只是在标头中定义为私有成员。该引擎使用 DLL 文件作为扩展,添加键盘、鼠标、操纵杆和脚本支持,与许多其他扩展一样简单。虽然大多数 DLL 不直接使用 DX,但它们需要 DX 知识和链接,只是因为它们引入了公开 DX 的标头。
添加 DX 11 后,这种复杂性会急剧增加,但没有必要。将 DX 成员移至仅在源中定义的 PIMPL 中,消除了这种强制。
除了减少库依赖性之外,当我将私有成员函数移至 PIMPL 中时,我公开的接口变得更加清晰,仅公开前端接口。
Here is an actual scenario I encountered, where this idiom helped a great deal. I recently decided to support DirectX 11, as well as my existing DirectX 9 support, in a game engine.
The engine already wrapped most DX features, so none of the DX interfaces were used directly; they were just defined in the headers as private members. The engine uses DLL files as extensions, adding keyboard, mouse, joystick, and scripting support, as week as many other extensions. While most of those DLLs did not use DX directly, they required knowledge and linkage to DX simply because they pulled in headers that exposed DX.
In adding DX 11, this complexity was to increase dramatically, however unnecessarily. Moving the DX members into a PIMPL, defined only in the source, eliminated this imposition.
On top of this reduction of library dependencies, my exposed interfaces became cleaner as I moved private member functions into the PIMPL, exposing only front facing interfaces.
正如许多其他人所说,Pimpl 惯用法允许实现完整的信息隐藏和编译独立性,但不幸的是,代价是性能损失(额外的指针间接寻址)和额外的内存需求(成员指针本身)。在嵌入式软件开发中,额外的成本可能至关重要,特别是在必须尽可能节省内存的情况下。
使用 C++ 抽象类作为接口将以相同的成本获得相同的好处。
这实际上表明了 C++ 的一个很大缺陷,即如果不重复类似 C 的接口(以不透明指针作为参数的全局方法),就不可能在没有额外资源缺点的情况下实现真正的信息隐藏和编译独立性:这主要是因为类的声明必须由其用户包含,它不仅导出用户需要的类的接口(公共方法),而且还导出用户不需要的类的内部结构(私有成员)。
As many other said, the Pimpl idiom allows to reach complete information hiding and compilation independency, unfortunately with the cost of performance loss (additional pointer indirection) and additional memory need (the member pointer itself). The additional cost can be critical in embedded software development, in particular in those scenarios where memory must be economized as much as possible.
Using C++ abstract classes as interfaces would lead to the same benefits at the same cost.
This shows actually a big deficiency of C++ where, without recurring to C-like interfaces (global methods with an opaque pointer as parameter), it is not possible to have true information hiding and compilation independency without additional resource drawbacks: this is mainly because the declaration of a class, which must be included by its users, exports not only the interface of the class (public methods) needed by the users, but also its internals (private members), not needed by the users.
我看到的一个好处是它允许程序员以相当快的方式实现某些操作:
PS:我希望我没有误解移动语义。
One benefit I can see is that it allows the programmer to implement certain operations in a fairly fast manner:
PS: I hope I'm not misunderstanding move semantics.
它在很多项目的实践中都有使用。它的实用性在很大程度上取决于项目的类型。使用此功能的更突出的项目之一是Qt,其基本思想是对用户(使用 Qt 的其他开发人员)隐藏实现或特定于平台的代码。
这是一个崇高的想法,但有一个真正的缺点:调试
只要隐藏在私有实现中的代码具有优质的质量,这一切都很好,但是如果其中存在错误,那么用户/开发人员就会遇到问题,因为它只是指向隐藏实现的愚蠢指针,即使他/她有实现源代码。
因此,几乎所有的设计决策都有利有弊。
It is used in practice in a lot of projects. It's usefulness depends heavily on the kind of project. One of the more prominent projects using this is Qt, where the basic idea is to hide implementation or platform-specific code from the user (other developers using Qt).
This is a noble idea, but there is a real drawback to this: debugging
As long as the code hidden in private implementations is of premium quality this is all well, but if there are bugs in there, then the user/developer has a problem, because it just a dumb pointer to a hidden implementation, even if he/she has the implementations source code.
So as in nearly all design decisions there are pros and cons.