我正在编写一个C++标头,我在其中定义了一个
class A {
// ...
};
Run Code Online (Sandbox Code Playgroud)
我想隐藏在外面的世界(因为它可能会更改甚至在此标题的未来版本中被删除).
在同一个头文件中还有一个B类,它有一个A类对象作为成员:
class B {
public:
// ...
private:
A a_;
};
Run Code Online (Sandbox Code Playgroud)
什么是从外界隐藏A级的正确方法?
如果我将A的定义放在未命名的命名空间中,编译器会发出警告,所以我认为,由于内部链接的问题,我应该做其他事情.
.H
public:
void doStuff() const;
private:
struct Private;
Private * d;
Run Code Online (Sandbox Code Playgroud)
的.cpp
struct XX::Private
{
int count;
}
void XX::doStuff() const
{
d->count = 2; // I want an error here!!
}
Run Code Online (Sandbox Code Playgroud)
你需要更好的解释吗?
更新:
我以为我会做一些不同的事情,需要对代码进行较少的更改.我做的:
.H
template <class TPriv>
class PrivatePtr
{
public:
...
TPriv * const operator->();
TPriv const * const operator->() const;
...
private:
TPriv * m_priv;
};
Run Code Online (Sandbox Code Playgroud)
的.cpp
...
template <class TPriv>
TPriv * const PrivatePtr<TPriv>::operator->()
{
return m_priv;
}
template <class TPriv>
TPriv const * const …Run Code Online (Sandbox Code Playgroud) 如果你有一个对象B需要一个对象A的私有成员的副本,并且私有成员被Pimpl隐藏,那么如何在不暴露内部的情况下实现它呢?// Foo.h
class Foo
{
private :
struct impl ;
impl * pimpl ;
};
// Foo.cpp
struct impl { std::string data; }
//main.cpp
Foo A;
Foo B;
// I want A::pimpl->data copied to B::pimpl->data and I don't want std::string exposed in my Foo header.
Run Code Online (Sandbox Code Playgroud) 考虑下一个简单的例子:
标题:
// a.hpp
#ifndef A_HPP
#define A_HPP
#include <memory>
class A
{
public:
A();
int foo();
private:
struct Imp;
std::auto_ptr< Imp > pimpl;
};
#endif // A_HPP
Run Code Online (Sandbox Code Playgroud)
实施:
// a.cpp
#include "a.hpp"
struct A::Imp
{
int foo()
{
// do something and return the result
}
};
A::A() : pimpl( new Imp )
{}
int A::foo()
{
return pimpl->foo();
}
Run Code Online (Sandbox Code Playgroud)
主要的 :
// main.cpp
#include "header.hpp"
int main()
{
A a;
return a.foo();
}
Run Code Online (Sandbox Code Playgroud)
问题是:
方法是否A::Imp::foo会被内联到A::foo …
我知道有很多关于这个主题的帖子,但我很难找到这个问题的答案.
对于更快的函数调用,纯虚拟接口还是pimpl?
乍一看,在我看来,纯虚拟接口会更快,因为使用pimpl会花费两个函数调用而不是一个......或者在这种情况下某些聪明的编译器技巧会接管吗?
编辑:我正在尝试决定我应该使用哪些来抽象出几个对象的系统相关部分,这些部分可能最终必须经常产生,而且数量很多.
编辑:
我想在这一点上值得一提,我的问题的根源在于我错误地将抽象工厂设计模式误认为是使我的代码在多个平台上工作的方法,当它的真正目的是切换给定接口的实现时在运行时.
c++ performance virtual-functions cross-platform pimpl-idiom
我为证明问题所需的大量代码道歉.我在使用带有std :: unique_ptr的pimpl习惯用法时遇到了问题.具体地说,当一个类(具有pimpl'ed实现)被用作具有pimpl'ed实现的另一个复合类中的成员数据时,似乎会出现问题.
我能够找到的大部分答案都是缺少明确的析构函数声明,但正如你在这里看到的,我已经声明并定义了析构函数.
这段代码有什么问题,可以修改它来编译而不改变设计吗?
注意:错误似乎发生在SomeComposite :: getValue()的定义中,并且编译器在编译时才能看到错误.在memory.h中遇到错误,消息是'sizeof'的无效应用程序到不完整类型'pimplproblem :: SomeInt :: impl'.
SomeInt.h
#pragma once
#include <iostream>
#include <memory>
namespace pimplproblem
{
class SomeInt
{
public:
explicit SomeInt( int value );
SomeInt( const SomeInt& other ); // copy
SomeInt( SomeInt&& other ) = default; // move
virtual ~SomeInt();
SomeInt& operator=( const SomeInt& other ); // assign
SomeInt& operator=( SomeInt&& other ) = default; // move assign
int getValue() const;
private:
class impl;
std::unique_ptr<impl> myImpl;
};
} …Run Code Online (Sandbox Code Playgroud) 这不是std :: unique_ptr的欺骗,不完整的类型将无法编译.
请考虑以下代码:
#include <memory>
struct X
{
X();
~X();
struct Impl;
std::unique_ptr<Impl> up_;
};
struct Impl {}; // fully visible here
X::X() : up_{nullptr}{}
X::~X() = default;
int main()
{
X x;
}
Run Code Online (Sandbox Code Playgroud)
gcc/clang都吐出错误说不Impl完整.但是,我提供了一个默认的析构函数,X 后面 Impl是完全可见的,所以IMO代码应该编译.为什么不呢?现在出人意料:如果我做Impl了一个内部类,即定义
struct X::Impl{};
Run Code Online (Sandbox Code Playgroud)
我对 Qt 和 pimpl 的问题实际上不是问题,而是对最佳实践建议的请求。
所以:我们有一个相当大的项目,有很多 GUI 和其他 Qt 类。良好的协作需要标头的可读性,减少编译时间也是经常考虑的问题。
因此,我有很多课程,例如:
class SomeAwesomeClass: public QWidget
{
Q_OBJECT
public:
/**/
//interface goes here
void doSomething();
...
private:
struct SomeAwesomeClassImpl;
QScopedPointer<SomeAwesomeClassImpl> impl;
}
Run Code Online (Sandbox Code Playgroud)
当然,Pimpl 类在 .cpp 文件中,工作正常,例如:
struct MonitorForm::MonitorFormImpl
{
//lots of stuff
}
Run Code Online (Sandbox Code Playgroud)
这个软件应该是跨平台的(这并不奇怪)并且无需大量工作即可交叉编译。我知道 Q_DECLARE_PRIVATE、Q_D 和其他宏,它们让我更多地考虑 Qt MOC,Qt 版本中可能存在的差异(由于遗留代码),但是这样或那样的方式有很多行代码 contatinig 之类的
impl->ui->component->doStuff();
//and
impl->mSomePrivateThing->doOtherStuff()
//and even
impl->ui->component->SetSomething(impl->mSomePrivateThing->getValue());
Run Code Online (Sandbox Code Playgroud)
上面的伪代码是真实代码的简化版本,但我们大多数人都可以接受。但一些同事坚持认为,编写和阅读所有这些长行相当麻烦,尤其是在impl->ui->mSomething->重复过于频繁的情况下。意见指出,Qt marcos 最终也为这种情况添加了视觉垃圾。Seversl#define可以提供帮助,但这些通常被认为是不好的做法。
简而言之,根据您的经验,有没有办法让 pimpl 使用更简洁?例如,在非图书馆课程中,也许并不像看起来那样经常需要它?也许它的使用目标不一样,取决于情况?
无论如何,正确的烹饪方法是什么?
Qt在开发过程中大量使用了PIMPL习惯用法:https://wiki.qt.io/D-Pointer
正如我在这里所读到的:“ d指针”源于Trolltech的Arnt Gulbrandsen,他首先将该技术引入了Qt,使其成为最早的C ++ GUI库之一,即使在更大的发行版之间也保持二进制兼容性。” 。但是没有人说“ D”代表什么。
那么“ D”在D-Pointer中代表什么?
显然,pimpl 并不是绝对必要的,但是如果课程是按照“正常”方式设计的,那么,似乎搬家不会给您带来全部预期的好处:搬家应该便宜;特别是,它应该比复制快得多;成本不应随着“真实”内部数据的数量而“扩大”。理想情况下,成本应该是 O(1)。
显然,如果您使用 pimpl,您每次都会以最少的努力和最大的可靠性获得这种速度优势(感谢= default)。那么,当您想要移动物体的能力时,是否有任何理由不只是在整个地方做 pimpl 呢?
(我假设您可以在应用程序中使用堆,因为这显然排除了 pimpl。)
c++ ×10
pimpl-idiom ×10
c++11 ×2
qt ×2
d-pointer ×1
header ×1
inline ×1
namespaces ×1
performance ×1
qt5 ×1
unique-ptr ×1