pimpl习语中使用的实现类是否有任何理由可以拥有任何私有成员?我真正想到的唯一原因是保护自己免受自己的伤害 - 即私人成员用于在类和用户之间强制执行某种契约,在这种情况下,类和用户是密切相关的,所以它似乎没必要.
我正在开发一个为某些服务定义客户端接口的库.在引擎盖下,我必须验证用户提供的数据,然后使用来自另一个库的Connection类将其传递给"引擎"进程(注意:我们的库的用户不知道Connection类).我的一位同事建议使用PIMPL:
class Client {
public:
Client();
void sendStuff(const Stuff &stuff) {_pimpl->sendStuff(stuff);}
Stuff getStuff(const StuffId &id) {return _pimpl->getStuff(id);}
private:
ClientImpl *_pimpl;
}
class ClientImpl { // not exported
public:
void sendStuff(const Stuff &stuff);
Stuff getStuff(const StuffId &id);
private:
Connection _connection;
}
Run Code Online (Sandbox Code Playgroud)
但是,我发现很难测试 - 即使我将测试链接到一些模拟的Connection实现,我也无法轻松访问它来设置和验证期望.我错过了什么,或者更清洁,更可测试的解决方案是使用interface + factory:
class ClientInterface {
public:
void sendStuff(const Stuff &stuff) = 0;
Stuff getStuff(const StuffId &id) = 0;
}
class ClientImplementation : public ClientInterface { // not exported
public:
ClientImplementation(Connection *connection);
// +implementation of ClientInterface
}
class …Run Code Online (Sandbox Code Playgroud) 所以我一直在考虑PIMPL和堆栈分配.我一直在写一个库,并决定使用PIMPL隐藏该类的私有成员.这意味着我会有一个像这样声明的类
class Foo {
private:
class Handle;
std::tr1::shared_ptr<Handle> handle;
public:
Foo();
};
Run Code Online (Sandbox Code Playgroud)
这很直接.但是然后在构造函数中你这样做
Foo::Foo() : handle(new Handle()) {}
Run Code Online (Sandbox Code Playgroud)
因此,当使用我的库的人在堆栈上创建Foo时,他们实际上是在进行堆分配.这是使用PIMPL时必须要考虑的权衡吗?我想在构造函数旁边发出警告释放文档:"警告:这会导致堆分配"或者其他一些问题.
我的另一个想法是将所有暴露给实现的类作为纯虚拟接口和一大堆静态工厂方法返回智能指针.这也意味着堆分配,但没有技巧.
有什么想法或建议吗?我是否过度考虑使用我的图书馆的程序员?
当使用带有pImpl惯用语的智能指针时,如
struct Foo
{
private:
struct Impl;
boost::scoped_ptr<Impl> pImpl;
};
Run Code Online (Sandbox Code Playgroud)
显而易见的问题是Foo::Impl在Foo生成析构函数时不完整.
编译器通常在那里发出警告,并且boost::checked_delete由Boost智能指针在内部使用,静态断言该类Foo::Impl是完整的,如果不是这样则触发错误.
对于上面的编译示例,因此必须编写
struct Foo
{
~Foo();
private:
struct Impl;
boost::scoped_ptr<Impl> pImpl;
};
Run Code Online (Sandbox Code Playgroud)
并Foo::~Foo在实现文件中实现一个空,Foo::Impl完成的地方.这是智能指针优于裸指针的优势,因为我们不能无法实现析构函数.
到现在为止还挺好.但是当我尝试在类似的Bar类中引入模板构造函数时,我遇到了一种奇怪的行为(完整代码,请亲自尝试):
// File Bar.h
#ifndef BAR_H
#define BAR_H 1
#include <vector>
#include <boost/scoped_ptr.hpp>
struct Bar
{
template <typename I>
Bar(I begin, I end);
~Bar();
private:
struct Impl;
boost::scoped_ptr<Impl> pImpl;
void buildImpl(std::vector<double>&);
};
template <typename I>
Bar::Bar(I begin, I end)
{
std::vector<double> tmp(begin, …Run Code Online (Sandbox Code Playgroud) 我试图使用pimpl模式并在匿名命名空间中定义实现类.这在C++中是否可行?我失败的尝试描述如下.
是否可以在不将实现移动到具有名称(或全局名称)的命名空间的情况下解决此问题?
class MyCalculatorImplementation;
class MyCalculator
{
public:
MyCalculator();
int CalculateStuff(int);
private:
MyCalculatorImplementation* pimpl;
};
namespace // If i omit the namespace, everything is OK
{
class MyCalculatorImplementation
{
public:
int Calculate(int input)
{
// Insert some complicated calculation here
}
private:
int state[100];
};
}
// error C2872: 'MyCalculatorImplementation' : ambiguous symbol
MyCalculator::MyCalculator(): pimpl(new MyCalculatorImplementation)
{
}
int MyCalculator::CalculateStuff(int x)
{
return pimpl->Calculate(x);
}
Run Code Online (Sandbox Code Playgroud) // main_pimpl_sample.cpp
#include "pimpl_sample.hpp"
using namespace std;
int main()
{
pimpl_sample p;
return 0;
}
// pimpl_sample.cpp
#include "pimpl_sample.hpp"
struct pimpl_sample::impl {
};
pimpl_sample::pimpl_sample()
: pimpl_(new impl) {
}
// pimpl_sample::~pimpl_sample()
// cause problem if missed
// {}
// pimpl_sample.hpp
#if !defined (PIMPL_SAMPLE)
#define PIMPL_SAMPLE
#include <boost/scoped_ptr.hpp>
class pimpl_sample {
struct impl;
boost::scoped_ptr<impl> pimpl_;
public:
pimpl_sample();
//~pimpl_sample(); cause problem if missed
void do_something();
};
#endif
~/Documents/C++/boost $ g++ --version
g++ (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2
~/Documents/C++/boost $ g++ -o main_pimpl_sample main_pimpl_sample.cpp pimpl_sample.cpp …Run Code Online (Sandbox Code Playgroud) 传统的PImpl Idiom是这样的:
#include <memory>
struct Blah
{
//public interface declarations
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//in source implementation file:
struct Blah::Impl
{
//private data
};
//public interface definitions
Run Code Online (Sandbox Code Playgroud)
但是,为了好玩,我尝试使用具有私有继承的组合:
[Test.h]
#include <type_traits>
#include <memory>
template<typename Derived>
struct PImplMagic
{
PImplMagic()
{
static_assert(std::is_base_of<PImplMagic, Derived>::value,
"Template parameter must be deriving class");
}
//protected: //has to be public, unfortunately
struct Impl;
};
struct Test : private PImplMagic<Test>,
private std::unique_ptr<PImplMagic<Test>::Impl>
{
Test();
~Test();
void f();
};
Run Code Online (Sandbox Code Playgroud)
[第一个翻译单位]
#include "Test.h"
int main() …Run Code Online (Sandbox Code Playgroud) 假设我有一个私有成员的类,这是该类客户端不关心的实现细节.这个类是一个值类型,我们希望它是可复制的,例如
#include <boost/bimap.hpp> // some header that pulls in many other files
class MyClass {
public:
MyClass() {}
...
private:
boost::bimap<Key,Value> table;
};
Run Code Online (Sandbox Code Playgroud)
现在,MyClass的每个客户端都被迫引入了许多不需要的升级头,增加了构建时间.但是,该课程至少是可复制的.
如果我们引入编译器防火墙(Pimpl idiom),那么我们可以将#include依赖项移动到cpp文件,但是由于规则5,我们现在必须做更多的努力工作:
// no extra #includes - nice
class MyClass {
public:
MyClass() {}
// ugh, I don't want this, just make it copyable!
MyClass(const MyClass& rhs);
MyClass(MyClass&& rhs);
MyClass& operator=(const MyClass& rhs);
MyClass& operator=(MyClass&& rhs);
~MyClass() {}
...
private:
std::unique_ptr<MyClassImpl> impl;
};
Run Code Online (Sandbox Code Playgroud)
是否有一种技术可以获得编译器防火墙的好处,但保留可复制性,以便我不需要包含5的规则样板?
我正在阅读Scott Meyers撰写的Effective Modern C++,他正在讨论使用pimpl习语并指向实现类unique_ptr,但是存在需要完整类型的特殊成员函数(例如析构函数)的问题.这是因为unique_ptr在delete p使用之前,默认删除器静态断言要删除的类型是否完整.所以任何类特殊的成员函数必须在实现文件中定义(而不是编译器生成的),之后实现类已定义.
在本章的最后,他提到如果使用的是智能指针,则不需要在实现文件中定义特殊的成员函数shared_ptr,这源于它支持自定义删除器的方式.报价:
pImpl指针的std :: unique_ptr和std :: shared_ptr之间的行为差异源于这些智能指针支持自定义删除器的不同方式.对于std :: unique_ptr,删除器的类型是智能指针类型的一部分,这使编译器可以生成更小的运行时数据结构和更快的运行时代码.这种更高效率的结果是,当使用编译器生成的特殊函数(例如,析构函数或移动操作)时,指向类型必须是完整的.对于std :: shared_ptr,删除器的类型不是智能指针类型的一部分.这需要更大的运行时数据结构和稍慢的代码,但是当使用编译器生成的特殊函数时,指向的类型不需要完整.
尽管如此,我仍然不明白为什么shared_ptr没有完成课程仍然可以工作.这似乎是使用时没有编译器错误的唯一原因shared_ptr是因为没有像has那样的静态断言unique_ptr,并且由于缺少断言而可能会发生未定义的运行时行为.
我不知道shared_ptr析构函数的实现,但是(从阅读C++ Primer)我收集的印象它的工作原理如下:
del ? del(p) : delete p;
Run Code Online (Sandbox Code Playgroud)
del自定义删除器的指针或函数对象在哪里.Cppreference还清楚地说明shared_ptr了没有自定义删除器使用的析构函数delete p
3)
delete ptr如果T不是数组类型,则使用delete-expression ; .... Y必须是完整的类型.删除表达式必须格式正确,具有明确定义的行为并且不会抛出任何异常.
强调删除类型必须完整的事实.pimpl习语的最小例子:
//widget.h
#ifndef WIDGET
#define WIDGET
#include <memory>
class Widget{
public:
Widget();
private:
struct Impl;
std::shared_ptr<Impl> pImpl;
};
#endif // …Run Code Online (Sandbox Code Playgroud) 我一直在学习如何使用Herb Sutter在本页描述的更新的c ++ 11方法来实现pimpl习语:https://herbsutter.com/gotw/_100/
我试图通过向私有实现添加成员变量来修改此示例,特别是std :: string(尽管char*具有相同的问题).
由于使用了静态const非整数类型,这似乎是不可能的.类内初始化只能对整数类型进行,但由于它是静态的,因此也无法在构造函数中初始化.
这个问题的解决方案是在头文件中声明私有变量,并在实现中初始化它,如下所示: C++ static constant string(class member)
但是,这个解决方案对我不起作用,因为它打破了我试图通过pimpl习语实现的封装.
在使用pimpl习语时,如何在隐藏的内部类中隐藏非整数静态const变量?
这是我能想出的最简单(不正确)的例子:
Widget.h:
#ifndef WIDGET_H_
#define WIDGET_H_
#include <memory>
class Widget {
public:
Widget();
~Widget();
private:
class Impl;
std::unique_ptr<Impl> pimpl;
};
#endif
Run Code Online (Sandbox Code Playgroud)
Widget.cpp:
#include "Widget.h"
#include <string>
class Widget::Impl {
public:
static const std::string TEST = "test";
Impl() { };
~Impl() { };
};
Widget::Widget() : pimpl(new Impl()) { }
Widget::~Widget() { }
Run Code Online (Sandbox Code Playgroud)
编译命令:
g++ -std=c++11 -Wall -c …Run Code Online (Sandbox Code Playgroud) c++ ×10
pimpl-idiom ×10
c++11 ×3
composition ×1
heapalloc ×1
namespaces ×1
stackalloc ×1
unit-testing ×1