如何为含模板成员函数的类实现Pimpl惯用法?
问题描述
我有一个包含模板成员函数的类,希望通过Pimpl惯用法将私有成员放入实现类中,当前代码结构如下:
Foo.hpp
#include <iostream> class Foo { public: private: class FooImpl; std::unique_ptr<FooImpl> impl_; public: Foo() = default; template <class F, class... Args> auto SomeFunction ( F &&f, Args &&...args) -> boost::unique_future<decltype (f(args...))>; };
Foo.cpp
#include <Foo.hpp> #include <FooImpl.hpp> template <class... Args> template <class F, class... Args> auto SomeFunction ( F &&f, Args &&...args) -> boost::unique_future<decltype (f(args...))> { impl->SomeFunction(std::forward<F>(f),std::forward<Args>(args)...); }
FooImpl.hpp
#include <Foo.hpp> class Foo::FooImpl { public: FooImpl() = default; template <class F, class... Args> auto SomeFunction ( F &&f, Args &&...args) -> boost::unique_future<decltype (f(args...))>; private: int dummy; };
FooImpl.cpp
#include <FooImpl.hpp> template <class... Args> template <class F, class... Args> auto SomeFunction ( F &&f, Args &&...args) -> boost::unique_future<decltype (f(args...))> { //Do Something... }
我尝试过以下方法但均不可行:
- 使用虚函数:因虚函数无法模板化而失败。
- 显式实例化所有模板实例:由于函数可能接收任意类型,无法穷举实例化,因此不适用。
请问是否有其他实现方法?能否为模板函数实现Pimpl惯用法?或者有没有Pimpl惯用法的替代方案?
解决方案
1. 拆分模板与非模板逻辑,保留Pimpl核心
Pimpl的核心是隐藏非模板的私有实现细节,而模板函数本身必须在头文件中可见才能被编译器实例化。可以将模板函数的转发逻辑留在头文件,把真正的业务逻辑封装到FooImpl的非模板成员中:
修改后的Foo.hpp:
#include <iostream> #include <memory> #include <boost/thread/future.hpp> class Foo { private: class FooImpl; std::unique_ptr<FooImpl> impl_; public: Foo(); ~Foo(); // 必须在cpp中定义,因为unique_ptr需要FooImpl的完整类型 template <class F, class... Args> auto SomeFunction(F&& f, Args&&... args) -> boost::unique_future<decltype(f(args...))> { return impl_->SomeFunction(std::forward<F>(f), std::forward<Args>(args)...); } }; // 将FooImpl的定义放在头文件末尾,避免循环依赖 class Foo::FooImpl { public: FooImpl() = default; template <class F, class... Args> auto SomeFunction(F&& f, Args&&... args) -> boost::unique_future<decltype(f(args...))> { // 调用非模板的核心逻辑函数,实现细节放在cpp中 return runTask(std::bind(std::forward<F>(f), std::forward<Args>(args)...)); } private: int dummy; // 非模板的核心逻辑,实现放在cpp里 template <class R> boost::unique_future<R> runTask(std::function<R()> task); };
Foo.cpp中实现非模板逻辑和类的构造析构:
#include "Foo.hpp" #include <boost/thread/thread.hpp> Foo::Foo() : impl_(std::make_unique<FooImpl>()) {} Foo::~Foo() = default; template <class R> boost::unique_future<R> Foo::FooImpl::runTask(std::function<R()> task) { // 这里写具体的异步逻辑或业务处理 return boost::async(boost::launch::async, std::move(task)); } // 显式实例化常用的返回类型,避免重复编译 template boost::unique_future<int> Foo::FooImpl::runTask(std::function<int()>); template boost::unique_future<void> Foo::FooImpl::runTask(std::function<void()>);
2. 用类型擦除封装模板逻辑
如果必须将所有实现移出头文件,可以通过类型擦除把模板参数转为非模板的虚函数接口,绕过Pimpl与模板的冲突:
首先在FooImpl.hpp中定义类型擦除的基类:
#include <boost/thread/future.hpp> #include <functional> #include <memory> class TaskBase { public: virtual ~TaskBase() = default; virtual boost::unique_future<void> execute() = 0; }; template <class F, class... Args> class TaskWrapper : public TaskBase { public: TaskWrapper(F&& f, Args&&... args) : task_(std::bind(std::forward<F>(f), std::forward<Args>(args)...)) {} boost::unique_future<void> execute() override { auto fut = boost::async(boost::launch::async, std::move(task_)); return fut.then([](boost::unique_future<void>) {}); } private: std::function<void()> task_; };
修改FooImpl为非模板接口:
#include "Foo.hpp" #include "TaskBase.hpp" class Foo::FooImpl { public: FooImpl() = default; boost::unique_future<void> SomeFunction(std::unique_ptr<TaskBase> task) { return task->execute(); } };
最后在Foo.hpp的模板函数中创建TaskWrapper并转发:
template <class F, class... Args> auto Foo::SomeFunction(F&& f, Args&&... args) -> boost::unique_future<decltype(f(args...))> { auto task = std::make_unique<TaskWrapper<F, Args...>>(std::forward<F>(f), std::forward<Args>(args)...); // 可根据需求调整返回类型匹配逻辑 return impl_->SomeFunction(std::move(task)); }
这种方法会带来虚函数调用和内存分配的开销,但能完全隐藏实现细节。
3. 替代Pimpl:头文件拆分的编译防火墙
如果Pimpl不适合当前场景,可以用头文件拆分实现编译防火墙,无需指针开销:
Foo.hpp(对外暴露):
#include <iostream> #include <boost/thread/future.hpp> class Foo { private: int dummy; // 若无需完全隐藏,可直接放置成员;或用内部类封装 public: Foo() = default; template <class F, class... Args> auto SomeFunction(F&& f, Args&&... args) -> boost::unique_future<decltype(f(args...))>; };
Foo.inl(内部实现文件,不对外发布):
#include "Foo.hpp" #include <boost/thread/thread.hpp> template <class F, class... Args> auto Foo::SomeFunction(F&& f, Args&&... args) -> boost::unique_future<decltype(f(args...))> { // 实现具体逻辑 return boost::async(boost::launch::async, std::forward<F>(f), std::forward<Args>(args)...); }
在需要实例化模板的地方包含Foo.inl,既隐藏了实现细节,又避免了Pimpl的额外开销。
内容的提问来源于stack exchange,提问作者Tharani B
相关产品推荐
相关产品推荐

