依赖模板函数的 Pimpl 习语实现
Pimpl idiom implementation depending on a template function
请考虑以下代码。
模板参数是一个处理程序class,必须提供函数bar()
。
我正在使用 Pimpl 惯用法来隐藏 Foo
的实现细节。在使用模板参数之前,构造函数定义在 foo.cpp
中,一切都很好。
///////////
// foo.h
///////////
class Foo
{
public:
template <class Handler>
Foo(Handler& handler);
void someFunction();
private:
/** Private implementation details. */
struct Impl;
const std::unique_ptr<Impl> m_impl;
};
template <class Handler>
Foo::Foo(Handler& handler) : m_impl{new Impl{handler}}
{
}
///////////
// foo.cpp
///////////
#include "foo.h"
/** Encapsulates the private implementation details of Foo. */
struct Foo::Impl
{
public:
Impl(Handler& handler) : m_handler(handler)
{
}
void someOtherFunction()
{
m_handler->bar();
}
private:
Handler& m_handler;
friend class Foo;
};
void Foo::someFunction()
{
m_impl->someOtherFunction();
}
引入模板参数后,我不得不将构造函数放在 foo.h
中,这会导致以下编译器错误:
Allocation of incomplete type 'Foo::Impl'
.
我明白我收到错误的原因,但我想不出解决方法,仍然使用 Pimpl 习惯用法。
有人能帮忙吗?
你可以用或不用虚函数来解决这个问题。第一个选项稍微简单一些:使用虚函数,或者用于 Handler
class HandlerBase { .... virtual void bar() = 0; ... };
class Foo { ... std::unique_ptr<HandlerBase> handler; ... };
或 Impl
class ImplBase { ... virtual void someFunction() = 0; }
class Foo { ... std::unique_ptr<ImplBase> m_impl; ... }
template<typename Handler> class Impl : public ImplBase { ... Handler& m_handler; ...};
我认为这些实现起来相对简单。您可以看到,在使用 HandlerBase 时,您甚至不需要 pimpl 习惯用法。
另一种方法是使用 The Impossibly Fast C++ Delegates 方法:
class Foo
{
public:
template <class Handler>
explicit Foo(Handler& handler)
: handler(&handler) {
someFunctionInstance = someFunctionTemplate<Handler>;
}
void someFunction() {
someFunctionInstance(handler);
}
private:
typedef void (*SomeFunctionType)(void* handler);
void* handler;
SomeFunctionType someFunctionInstance;
template<typename Handler>
static void someFunctionTemplate(void* handler) {
static_cast<Handler*>(handler)->bar();
}
};
Foo 的类型仍然独立于 Handler。构造函数中的赋值创建了 someFunctionTemplate 的自定义实例,它能够调用实际的 Handler 类型。 SomeFunctionInstance 是 Handler 上的模板实例化的结果,但由于它是静态函数,因此其类型也独立于 Handler。
但是这个解决方案很棘手,并且确实包含 reinterpret_cast,它仍然是完全类型安全的。
一般来说,您需要某种类型的擦除。实际上,这意味着函数指针或虚函数(这只是使用函数指针实现的更高级别的抽象),如@tamas.kenez.
提供的三个解决方案
std::function
提供类型擦除,通常使用虚函数实现。这是使用 std::function
:
解决问题的另一种方法
////////////
// foo.h
class Foo
{
public:
template <class Handler>
Foo(Handler& handler) : Foo{ tag{},
[&handler](){handler.bar();} }
{}
// ...
private:
/** Private implementation details. */
struct Impl;
const std::unique_ptr<Impl> m_impl;
struct tag {};
Foo(tag, std::function<void()> bar);
};
////////////
// foo.cpp
struct Foo::Impl
{
Impl(std::function<void()> bar) : m_bar{bar}
{}
std::function<void()> m_bar;
// ...
};
Foo::Foo(Foo::tag, std::function<void()> bar) : m_impl{new Impl{bar}}
{}
我使用了标记类型来消除构造函数重载的歧义。
捕获对 handler
的引用的 lambda 存储在 std::function
中,以便 m_impl
的初始化可以从模板中分解出来。
请考虑以下代码。
模板参数是一个处理程序class,必须提供函数bar()
。
我正在使用 Pimpl 惯用法来隐藏 Foo
的实现细节。在使用模板参数之前,构造函数定义在 foo.cpp
中,一切都很好。
///////////
// foo.h
///////////
class Foo
{
public:
template <class Handler>
Foo(Handler& handler);
void someFunction();
private:
/** Private implementation details. */
struct Impl;
const std::unique_ptr<Impl> m_impl;
};
template <class Handler>
Foo::Foo(Handler& handler) : m_impl{new Impl{handler}}
{
}
///////////
// foo.cpp
///////////
#include "foo.h"
/** Encapsulates the private implementation details of Foo. */
struct Foo::Impl
{
public:
Impl(Handler& handler) : m_handler(handler)
{
}
void someOtherFunction()
{
m_handler->bar();
}
private:
Handler& m_handler;
friend class Foo;
};
void Foo::someFunction()
{
m_impl->someOtherFunction();
}
引入模板参数后,我不得不将构造函数放在 foo.h
中,这会导致以下编译器错误:
Allocation of incomplete type 'Foo::Impl'
.
我明白我收到错误的原因,但我想不出解决方法,仍然使用 Pimpl 习惯用法。
有人能帮忙吗?
你可以用或不用虚函数来解决这个问题。第一个选项稍微简单一些:使用虚函数,或者用于 Handler
class HandlerBase { .... virtual void bar() = 0; ... };
class Foo { ... std::unique_ptr<HandlerBase> handler; ... };
或 Impl
class ImplBase { ... virtual void someFunction() = 0; }
class Foo { ... std::unique_ptr<ImplBase> m_impl; ... }
template<typename Handler> class Impl : public ImplBase { ... Handler& m_handler; ...};
我认为这些实现起来相对简单。您可以看到,在使用 HandlerBase 时,您甚至不需要 pimpl 习惯用法。
另一种方法是使用 The Impossibly Fast C++ Delegates 方法:
class Foo
{
public:
template <class Handler>
explicit Foo(Handler& handler)
: handler(&handler) {
someFunctionInstance = someFunctionTemplate<Handler>;
}
void someFunction() {
someFunctionInstance(handler);
}
private:
typedef void (*SomeFunctionType)(void* handler);
void* handler;
SomeFunctionType someFunctionInstance;
template<typename Handler>
static void someFunctionTemplate(void* handler) {
static_cast<Handler*>(handler)->bar();
}
};
Foo 的类型仍然独立于 Handler。构造函数中的赋值创建了 someFunctionTemplate 的自定义实例,它能够调用实际的 Handler 类型。 SomeFunctionInstance 是 Handler 上的模板实例化的结果,但由于它是静态函数,因此其类型也独立于 Handler。
但是这个解决方案很棘手,并且确实包含 reinterpret_cast,它仍然是完全类型安全的。
一般来说,您需要某种类型的擦除。实际上,这意味着函数指针或虚函数(这只是使用函数指针实现的更高级别的抽象),如@tamas.kenez.
提供的三个解决方案std::function
提供类型擦除,通常使用虚函数实现。这是使用 std::function
:
////////////
// foo.h
class Foo
{
public:
template <class Handler>
Foo(Handler& handler) : Foo{ tag{},
[&handler](){handler.bar();} }
{}
// ...
private:
/** Private implementation details. */
struct Impl;
const std::unique_ptr<Impl> m_impl;
struct tag {};
Foo(tag, std::function<void()> bar);
};
////////////
// foo.cpp
struct Foo::Impl
{
Impl(std::function<void()> bar) : m_bar{bar}
{}
std::function<void()> m_bar;
// ...
};
Foo::Foo(Foo::tag, std::function<void()> bar) : m_impl{new Impl{bar}}
{}
我使用了标记类型来消除构造函数重载的歧义。
捕获对 handler
的引用的 lambda 存储在 std::function
中,以便 m_impl
的初始化可以从模板中分解出来。