(是的,我知道一台机器指令通常无关紧要.我问这个问题是因为我想了解pimpl习语,并以最好的方式使用它;而且因为有时我会关心一台机器指令.)
在下面的示例代码中,有两个类,Thing和
OtherThing.用户将包括"thing.hh".
Thing使用pimpl成语来隐藏它的实现.
OtherThing使用C样式 - 返回并获取指针的非成员函数.这种风格产生稍好的机器代码.我想知道:有没有办法使用C++风格 - 即,使函数成为成员函数 - 但仍然保存机器指令.我喜欢这种风格,因为它不会污染类外的命名空间.
注意:我只关注调用成员函数(在本例中calc).我不是在看对象分配.
下面是我的Mac上的文件,命令和机器代码.
thing.hh:
class ThingImpl;
class Thing
{
ThingImpl *impl;
public:
Thing();
int calc();
};
class OtherThing;
OtherThing *make_other();
int calc(OtherThing *);
Run Code Online (Sandbox Code Playgroud)
thing.cc:
#include "thing.hh"
struct ThingImpl
{
int x;
};
Thing::Thing()
{
impl = new ThingImpl;
impl->x = 5;
}
int Thing::calc()
{
return impl->x + 1;
}
struct OtherThing
{
int x;
};
OtherThing *make_other()
{
OtherThing *t …Run Code Online (Sandbox Code Playgroud) 当我使用pimpl习语时,将所有方法定义放在类定义中是否是个好主意?例如:
// in A.h
class A {
class impl;
boost::scoped_ptr<impl> pimpl;
public:
A();
int foo();
}
// in A.cpp
class A::impl {
// method defined in class
int foo() {
return 42;
}
// as opposed to only declaring the method, and defining elsewhere:
float bar();
};
A::A() : pimpl(new impl) { }
int A::foo() {
return pimpl->foo();
}
Run Code Online (Sandbox Code Playgroud)
据我所知,将方法定义放在类定义中的唯一问题是(1)实现在包含类定义的文件中可见,(2)编译器可以使方法内联.
在这种情况下,这些不是问题,因为类是在私有文件中定义的,并且内联没有任何效果,因为只在一个地方调用方法.
将定义放在类中的优点是您不必重复方法签名.
那么,这样可以吗?还有其他问题需要注意吗?
在Code Complete中的"Good Encapsulation"部分中,建议隐藏私有实现细节.C++中给出了一个例子.这个想法基本上是将接口与实现完全分开,即使在类级别也是如此.
class Employee {
public:
...
Employee( ... );
...
FullName GetName() const;
String GetAddress() const;
private:
EmployeeImplementation *m_implementation;
};
Run Code Online (Sandbox Code Playgroud)
这真的很好用吗?这不仅效率低下(这会带来什么样的性能损失?),但Code Complete("管理复杂性")的整个座右铭似乎已经被逆转 - 这不会增加复杂性吗?
我通常对pimpl使用boost :: scoped_ptr(出于一个原因,因为如果我忘记处理复制构造函数,我就不会感到惊讶)
但是,使用模板,我不能将析构函数放在完全定义impl的cpp文件中,以满足scoped_ptr析构函数的要求.无论如何它确实有效,但我不确定它是否能够保证工作或只是偶然.有一些"最佳实践"或标准吗?scoped_ptr是非可复制类中pimpls的最佳智能指针吗?
template <class T> class C {
public:
C(){}
~C(){}
private:
boost::scoped_ptr<T> pimpl_;
};
Run Code Online (Sandbox Code Playgroud) 我已经读过Pimpl有利于二进制兼容性,接口有利于轻松切换实现.我需要结合这两种技术,以便我的应用程序能够通过配置文件切换底层实现.
以下是我当前设计的布局:
Foo类:提供面向客户的API,我很担心ABI兼容性这里
一流的IFoo:接口类(所有的纯虚方法,虚析构函数)
类Vendor1Foo:实现IFoo的,使用的供应商1的库
类Vendor2Foo:实现IFoo的,使用供应商2的图书馆
通过不使用pimpl并严格使用接口,客户端代码可能如下所示:
IFoo* foo = new Vendor1Foo();
Run Code Online (Sandbox Code Playgroud)
问题是我的客户端代码根本无法了解Vendor1或Vendor2,而Foo只是我必须执行此操作的众多类之一.
我正在尝试做的所有概念如下:
class foo
{
private:
QScopedPointer<IFoo> pimpl;
void initImpl(); // Reads from QSettings and initializes pimpl
}
Run Code Online (Sandbox Code Playgroud)
有什么想法可以优雅地解决这个问题吗?
我希望能够提出一些宏或模板类/方法来帮助标准化我如何处理这个并最大限度地减少违反DRY.
模板类可以作为一个辅助性PIMPL像香草萨特的大上广义PIMPL方法对C++ 11:herbsutter.com/gotw/_101,它也必须包含逻辑实例根据配置的正确实施
这里有pimpl成语,桥梁模式和工厂模式的元素.在上面的例子中,initImpl()可以被认为是一个工厂方法.我正在寻找可能会或可能不会使用所有这些模式的解决方案.
我已经看过c ++ pimpl习语:实现取决于模板参数以及SO上的大多数pimpl习语问题.标题似乎很有希望,但它对我的特定用例没有帮助.
我不能使用C++ 11并使用Qt. D-Pointers无法解决我的问题,因为它们绑定到单个实现.
我在一些旧的库代码中捣乱,其基本目标是重构它.这个旧代码并不完全符合最佳实践和美观(是的 - 朋友很糟糕,并且在发现下面之后它已被删除 - 因为它是重构的疏忽).
现在准备运行一些单元测试我用clang ++,g ++和vc ++编译了代码(2005年 - 是的,我知道它已经过时了,但为了向后兼容 - 我必须这样做).
g ++和clang ++编译并运行没有错误,但Visual C++抱怨,所以在查看代码后,我发现了一些效果:
#include <iostream>
class one {
private:
struct private_impl;
private_impl* pimpl_;
public:
one();
~one();
void say_hello();
};
class two {
private:
friend class one;
void say_world();
public:
};
struct one::private_impl {
two t;
void say_world();
};
void one::private_impl::say_world() {
std::cout << " ";
t.say_world(); //This should not work should it?
}
one::one() : pimpl_(new private_impl) { }
one::~one() {
delete pimpl_;
}
void one::say_hello() …Run Code Online (Sandbox Code Playgroud) 在下面的例子中,如何正确调用~CImpl,但是当需要移动类时,编译器说它有一个不完整的类型?
如果Impl的声明被移动到它工作的标题,我的问题是如何将析构函数调用为好,所以它似乎不是类型不完整,但移动时出现问题.
档案:C.hpp
#include <memory>
class Impl;
class C
{
public:
C();
~C();
C(C&&) = default;
C& operator=(C&&) = default;
std::unique_ptr<Impl> p;
};
Run Code Online (Sandbox Code Playgroud)
档案C.cpp
#include "C.hpp"
#include <iostream>
using namespace std;
class Impl
{
public:
Impl() {}
virtual ~Impl() = default;
virtual void f() = 0;
};
class CImpl: public Impl
{
public:
~CImpl()
{
cout << "~CImpl()" << endl;
}
void f()
{
cout << "f()" << endl;
}
};
C::C():
p(new CImpl())
{}
C::~C()
Run Code Online (Sandbox Code Playgroud)
file:main.cpp
#include <iostream>
#include …Run Code Online (Sandbox Code Playgroud) pimpl(也称为编译器防火墙)习惯用于缩短编译时间,但代价是可读性和一点运行时性能.目前一个项目需要很长时间才能编译,如何衡量最佳的pimpl候选人?
我有使用pimpl的经验,将项目的编译时间从两小时缩短到十分钟,但我只是按照我的直觉做了这个:我推断出类头文件包括(1)很多源代码(2)复杂/模板类,是使用疙瘩成语的最佳人选.
是否有一个工具可以客观地指出哪些类是优秀的pimpl候选人?
只要我不将构造函数(of B)的定义移动到头部,代码就会工作B.h.
BH
class Imp; //<--- error here
class B{
public:
std::unique_ptr<Imp> imp;
B(); //<--- move definition to here will compile error
~B();
//// .... other functions ....
};
Run Code Online (Sandbox Code Playgroud)
B.cpp
#include "B.h"
#include "Imp.h"
B::B(){ }
~B::B(){ }
Run Code Online (Sandbox Code Playgroud)
Imp.h
class Imp{};
Run Code Online (Sandbox Code Playgroud)
Main.cpp (编译我)
#include "B.h"
Run Code Online (Sandbox Code Playgroud)
错误:删除指向不完整类型的指针
错误:使用未定义类型'Imp'C2027
我可以以某种方式理解析构函数必须被移动到.cpp,因为Imp可能会被称为析构: -
delete pointer-of-Imp; //something like this
Run Code Online (Sandbox Code Playgroud)
但是,我不明白为什么规则也涵盖了构造函数(问题).
我读过了 :-
.cpp.据我所知,pimpl 习惯用法将私有实现隐藏在前向声明的符号名称后面,以便可以在私有 cpp 模块中声明和使用它。
示例: https: //cpppatterns.com/patterns/pimpl.html
据我所知,因为 pimpl 类 hosint 需要了解其结构(大小、对齐方式),所以 pimpl 必须通过某种指针是间接的。
(或分配为足够大小的块,然后移动/创建到稍后通过强制转换重新解释的位置。)
即将推出的模块规范是否以任何方式解决这个问题?
c++ ×10
pimpl-idiom ×10
boost ×1
c++-modules ×1
c++11 ×1
friend ×1
inline ×1
interface ×1
optimization ×1
profiler ×1
qt ×1
scoped-ptr ×1
templates ×1
unique-ptr ×1