AAo*_*ris 6 c++ pimpl-idiom c++11
我一直在玩Pimpl成语并从中获得各种好处.我唯一不太关注的是我在定义函数时得到的感觉.
我真的很喜欢减少代码差异和冗余,当我必须在当前项目中相对复杂的Impls中添加或更改函数时,我觉得我的代码不够好.
我的问题是,有哪些有效的方法可以暗示或模板化我的类,如果我要定义一个新函数,我只需编写一个明确的定义和实现,其余的仍然在空间上接近于代码中的明确; 如果我要更改功能,必要的更改将尽可能少?
您可以考虑以下几点:
一个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_
Run Code Online (Sandbox Code Playgroud)
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)
{
}
Run Code Online (Sandbox Code Playgroud)
最后这是客户端代码的作用:
Main.cpp的
#include "Pimpl.h"
int main()
{
PublicImplementation pi; // not a pointer
pi->func_a(); // pointer semantics
pi->func_b();
}
Run Code Online (Sandbox Code Playgroud)
让我们假设你的标题开始是这样的:
class X
{
public:
...la de dah...
private:
struct Impl;
Impl* p_impl_;
};
Run Code Online (Sandbox Code Playgroud)
然后,当您添加功能时,您可以做出选择:
您是否让成员函数定义实现了引用各处事物的X
逻辑,或者p_impl_->
return p_impl->same_fn(all_the_args);
并将逻辑保留在Impl
类中?
如果您选择 1. 那么您最终会在标头中得到一个函数声明,并在匹配的实现文件中得到一个(比平常稍微混乱的)定义。
如果你选择 2. 那么你最终会在头文件中得到一个函数声明,在匹配的实现文件中得到一个包装/转发定义,并且至少在结构中得到一个定义Impl
(我倾向于不在Impl
类定义之外定义函数) - 这是一个实现细节,并且接口无论如何都不公开)。
通常没有可取的方法来改进这种情况(即构建过程中的宏黑客和额外的代码生成脚本有时可能是必要的,但很少见)。
整个堆可能并不重要,尽管第二种方法的变体可能是有趣的,即首先实现一个不使用pimpl 习惯用法的类(带有适当的标头和可选的内联函数),然后您可以包装它带有一个 pimpl 管理对象并将函数转发给它,这样您就可以自由地在某个地方拥有一些代码,有一天决定它想要使用该功能而不使用 pimpl 包装器,也许是为了提高性能/减少内存使用重新编译依赖项的成本。您还可以执行此操作以利用模板的特定实例化,而无需公开模板的代码。
为了说明这个选项(按照评论中的要求),让我们class X
从它自己的文件中的一个愚蠢的非 pimpl 开始,然后创建一个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; }
}
Run Code Online (Sandbox Code Playgroud)
如果您选择上述 2 的变体,这使得“实现”本身成为一个可用的实体,那么是的 - 您最终可能会得到与单个函数相关的 2 个声明和 2 个定义,但是其中一个定义将是一个简单的包装/转发函数,如果函数非常短且数量众多但有很多参数,那么它只会显着重复和乏味。