4

请考虑下面的代码。

模板参数是必须提供函数的处理程序类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 成语。

任何人都可以帮忙吗?

4

2 回答 2

3

您可以使用或不使用虚拟功能来解决此问题。第一个选项更容易一些:使用虚函数,无论是对于 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,它仍然是完全类型安全的。

于 2015-03-17T17:32:54.903 回答
2

一般来说,您需要某种类型的擦除。在实践中,这意味着函数指针或虚函数(它只是使用函数指针实现的更高级别的抽象),就像@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}}
{}

我使用了标记类型来消除构造函数重载的歧义。

捕获对 的引用的 lambdahandler存储在 中,std::function以便可以将 的初始化m_impl从模板中提取出来。

于 2015-03-17T18:30:02.857 回答