【发布时间】:2011-01-21 17:10:39
【问题描述】:
我刚刚注意到一个新术语 pimpl idiom,这个 idiom 与 Bridge 设计模式有什么区别?我对此感到困惑。
我还注意到 pimpl 惯用语总是用于交换功能,那是什么?谁能举个例子?
【问题讨论】:
标签: c++ design-patterns
我刚刚注意到一个新术语 pimpl idiom,这个 idiom 与 Bridge 设计模式有什么区别?我对此感到困惑。
我还注意到 pimpl 惯用语总是用于交换功能,那是什么?谁能举个例子?
【问题讨论】:
标签: c++ design-patterns
PIMPL 是一种隐藏实现的方式,主要是为了打破编译依赖。
另一方面,桥接模式是一种支持多种实现的方式。
swap 是标准 C++ 函数,用于交换两个对象的值。如果将指向实现的指针交换为不同的实现,则本质上是在运行时更改类的机制。
但在其基本和通用形式中,使用 PIMPL 的类指向单个实现,因此不存在具有不同子类的抽象类——只有一个类,前向声明并在别处编译。更改实现类不需要重新编译包含主标头的源代码。
例如,假设您有很多私有成员函数、私有枚举和私有数据。随着类的开发和维护,这些私有的“位”变化相当频繁。如果#include 依赖项使得接触此头文件会导致大量源代码被重新编译,那么您就有一个很好的 PIMPL 候选对象。
所以 Bridge 模式是关于面向对象的设计,而 PIMPL 成语是关于文件的物理设计。
(有关物理设计的更多信息,我推荐 John Lakos 的书 Large-Scale C++ Software Design。)
【讨论】:
我使用了指针 impl 来隐藏公共接口/包含文件中的实现细节 基本上界面是这样的:
struct Interface {
private:
class Impl;
Impl *pImpl;
};
然后在 Interface::Impl 内部的某个地方定义并实现,但细节没有暴露。
至于交换,这是我第一次听到它。能详细说明一下吗?
【讨论】:
Pimpl:简而言之,pimpl 模式非常适合隐藏私有实现。如果您想为需要针对您的公共接口构建的客户端公开您的头文件,但您不想公开您的私有实现细节,您可以使用 pimpl 模式来隐藏细节。您会这样做有几个原因,但主要是当您更改私有实现细节更改时,您的头文件不必更改,否则将迫使您的客户必须重新编译。通过这种方式,您可以解耦并隐藏您的私有实现细节。通常你应该像 unqiue 指针一样将 impl 指针保存在 RAII 容器中,以确保它在销毁时被释放。
Pimpl
// my_class.h
class my_class {
public:
// public here
private:
class impl; //forward declare impl
unique_ptr<impl> pimpl; // private implementation pointer
};
// my_class.cpp
class my_class::impl { // defined privately here
// ... all private data and functions: all of these
// can now change without recompiling callers ...
};
my_class::my_class(): pimpl( new impl )
{
// ... set impl values ...
my_class& operator=(my_class other);
friend void swap (my_class& lhs, myclass& rhs); // used for assignment
}
Swap 可能出现了,因为当您为此类执行赋值运算符并实现自己的交换例程来分配成员时,您也需要注意分配此指针
my_class& my_class::operator=(my_class other)
{
swap(*this,other);
return *this;
}
void swap ( my_class& lhs, myclass& rhs )
{
using std::swap // now use default swap if override doesn't exist
// call swap for other members
// swap (lhs.data,rhs.data);
// call swap on unique_ptr
lhs.pimpl.swap(rhs.pimpl); // doesn't throw exceptions
}
Bridge 是一种完全独立的模式,用于将项目连接在一起。模式之间的相似之处在于您的类中可能有一个隐藏实际调用的 process 方法,因为它将将此调用委托给将处理实际方法调用的包含对象。换句话说,如果您有一个包含请求实现基类指针的请求接口基类。因此,在运行时,您可以通过使用特定请求实现者类型初始化网桥来“桥接”系统,但调用网桥的人只会调用流程请求方法,该方法将在运行时将调用委托给特定的派生实现者请求方法。 google 上有几个来源有很好的图表,也可以更清楚地解释这一点。
【讨论】:
private 关键字将其保持为私有。如果您根本不想分配,可以通过设置 my_class & operator=(my_class const & other) = delete; 将其删除
目前正在审查一个实现Pimpl 模式的Boost 库。如果有人想在内部使用此代码,我已经使用建议的 Boost Pimpl 实现拼凑了一些基本示例:
https://github.com/sean-/Boost.Examples/tree/a148be39abcb21428857aa50495f8c352600741e/pimpl
更新:以上链接已更新为指向存档版本。 Boost.Pimpl 似乎不太可能在此时被接受以支持 std::unique_ptr<> 作为可行的替代品(尽管在一些不太常见的用例上不如 Boost.Pimpl 完整)。
如果您有权访问C++11,则最好使用std::unique_ptr<> 作为 PIMPL 实现:
class MyClass {
public:
// ...
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
您可以在我的C++11 reentrant class locking strategy 问题中查看使用std::unique_ptr<> 的完整示例。
使用std::unique_ptr<> 的swap() 方法,C++11 的移动语义将对象的内容从一个所有者移动到另一个所有者变得方便且非常实用。例如,假设MyClass 导出了一个swap() 实现,它只转发到std::unique_ptr<> 的swap:
void MyClass::swap(MyClass *c) { impl_.swap(c); }
MyClass c1, c2;
c1.swap(c2);
现在这是将c2 的内容传输到c1 的异常安全方式。使用 PIMPL 习语的另一个重要原因是保持/维护稳定的 ABI。
【讨论】:
C++11,则使用std::unique_ptr<> 可能会好得多。
嗯,这里是 PIMPL 成语:http://en.wikipedia.org/wiki/Opaque_pointer 它的作用很清楚。
而且桥接模式涉及更多 - 它不只是保存数据。 http://en.wikipedia.org/wiki/Bridge_pattern#C.2B.2B
【讨论】: