我一直在玩Pimpl成语并从中获得各种好处。我唯一对此过于热衷的是我在定义函数时得到的感觉。
我非常喜欢减少代码差异和冗余,当我在当前项目中即使是相对复杂的Impls中添加或更改函数时,我觉得我的代码还不够好。
我的问题是,有哪些有效的方法可以暗示或模板化我的类,如果我要定义一个新函数,我只需编写一个明确的定义和实现,并拥有其余的仍然在空间上接近代码中的explicits;如果我要更改功能,必要的更改将尽可能少?
答案 0 :(得分:8)
你可能会考虑以下几点:
用于最小化重复声明的Interface类。客户端将在其代码中使用PublicImplementation
类。
<强> Pimpl.h 强>
#ifndef PIMPL_H_
#define PIMPL_H_
#include <memory> // std::unique_ptr
class Interface
{
public:
virtual ~Interface() {}
virtual void func_a() = 0;
virtual void func_b() = 0;
};
class PublicImplementation
{
// smart pointer provides exception safety
std::unique_ptr<Interface> impl;
public:
PublicImplementation();
// pass-through invoker
Interface* operator->() { return impl.get(); }
};
#endif // PIMPL_H_
<强> Pimpl.cpp 强>
#include "Pimpl.h"
#include <iostream>
class PrivateImplementation
: public Interface
{
public:
void func_a() override { std::cout << "a" << '\n'; }
void func_b() override { std::cout << "b" << '\n'; }
};
PublicImplementation::PublicImplementation()
: impl(new PrivateImplementation)
{
}
最后这就是客户端代码的作用:
<强> Main.cpp的强>
#include "Pimpl.h"
int main()
{
PublicImplementation pi; // not a pointer
pi->func_a(); // pointer semantics
pi->func_b();
}
答案 1 :(得分:4)
让我们假设你的标题开头是这样的:
class X
{
public:
...la de dah...
private:
struct Impl;
Impl* p_impl_;
};
然后当您添加功能时,您可以选择:
您是否有X
成员函数定义实现逻辑,在整个地方引用p_impl_->
个事物,或者
return p_impl->same_fn(all_the_args);
并将逻辑保留在Impl
类中?
如果你选择1.那么你最终会在标题中添加一个函数声明,并在匹配的实现文件中找到一个(比通常稍微麻烦)的定义。
如果你选择2.那么你最终会在头文件中有一个函数声明,在匹配的实现文件中有一个包装/转发定义,并且至少在Impl
结构中有一个定义(我倾向于定义Impl
类定义之外的函数 - 它是一个实现细节,并且接口不是公共的。)
通常没有理由改进这种情况(即,构建过程中的宏hackery和额外的代码生成脚本有时可能是有保证的,但非常少)。
它可能并不重要,尽管第二种方法的变体可能是有意义的,首先要实现一个不使用pimpl习语的类(带有正确的标题)然后你可以使用pimpl管理对象包装它并将函数转发给它,这样你就可以自由地在某个地方某些地方决定它想要使用它不使用pimpl包装器的功能,可能是为了提高性能/减少内存使用,代价是重新编译依赖。您也可以使用模板的特定实例化而不暴露模板的代码。
为了说明这个选项(如评论中所要求的那样),让我们从它自己的文件中的一个愚蠢的非pimpl class X
开始,然后创建一个Pimpl::X
包装器(使用命名空间和相同的类名是完全可选的,但有助于翻转客户端代码以使用它们和提醒 - 这并不意味着简洁,这里的重点是让非pImpl版本也可用:)
// x.h
class X
{
public:
int get() const { return n_; } // inline
void operator=(int); // out-of-line definition
private:
int n_;
};
// x.c++
#include <x.h>
void X::operator=(int n) { n_ = n * 2; }
// x_pimpl.h
namespace Pimpl
{
class X
{
public:
X();
X(const X&);
~X();
X& operator=(const X&);
int get() const;
void operator=(int);
private:
struct Impl;
Impl* p_impl_;
};
}
x_pimpl.c++
#include <x.h>
namespace Pimpl
{
struct X::Impl
{
::X x_;
};
// the usual handling...
X() : p_impl_(new Impl) { }
X(const X& rhs) : p_impl(new Impl) { p_impl_->x_ = rhs.p_impl_->x_; }
~X() { delete p_impl_; }
X& operator=(const X& rhs) { p_impl_->x_ = rhs.p_impl_->x_; return *this; }
// the wrapping...
int X::get() const { return p_impl_->x_.get(); }
void X::operator=(int n) { p_impl_->x_ = n; }
}
如果您选择2上面的上述变体,这使得“实现”本身就是一个可用的实体,那么是 - 您可能最终得到2个声明和2个与单个函数相关的定义,但是定义将是一个简单的包装/转发函数,如果函数非常短且数量众多但参数很多,那么这个函数只会显着重复且繁琐。
答案 2 :(得分:3)
没有要求将IMPL对象视为相同的规则&amp;标准作为.h文件中的对象声明。通过允许成员变量公开(通过struct
声明),您不需要实现不必要的包装器层。这通常是安全的,因为只有.cpp文件才能访问IMPL。
考虑下面的代码,它可以实现PIMPL习惯用法的好处,而不会产生不必要的代码重复:
// x.h
class X {
public:
X();
~X();
X(const X&) = delete;
X& operator =(const X&) = delete;
void set(int val);
int get() const;
private:
struct IMPL;
IMPL* impl;
};
// x.cpp
#include "x.h"
struct X::IMPL {
int val;
};
X::X() : impl(new IMPL) {}
X::~X() { delete impl; }
void X::set(int val)
{
impl->val = val;
}
int X::get() const
{
return impl->val;
}
// main.cpp
#include <iostream>
#include "x.h"
int main (int, char *[])
{
X x;
x.set(10);
std::cout << x.get() << std::endl;
return 0;
}
答案 3 :(得分:1)
我只是开始通过sumarizing来确保我理解:你喜欢使用pimpl的好处,但是在添加或修改函数时不喜欢样板代码的数量?
简而言之,没有模板魔法可以用来消除这个样板,但是这里也有一些事情需要考虑: