c++ - 将 PIMPL 习惯用法与成员函数模板一起使用(事先不知道所有可能的数据类型)

标签 c++ templates pimpl-idiom

我正在尝试使用 PIMPL 惯用法实现一个通用接口(interface),它需要一些模板成员函数来为任何 类型提供功能。问题是,如果不预先声明函数可能使用的每种可能类型,我找不到任何方法来支持将模板函数与 PIMPL 习惯用法一起使用。

参见相关问题:Pimpl Idiom with template member function

该问题的可接受答案建议明确定义将与模板一起使用的每种类型。这并不总是实用的。考虑以下示例:

example.h

#ifndef EXAMPLE_H
#define EXAMPLE_H

#include <memory>

class Example
{
public:
    Example();
    ~Example();

    // Generic template function in the interface
    template <typename T>
    void PrintData(const T& data) const;

private:
    struct Impl;
    std::unique_ptr<Impl> m_impl;
};

#endif

example.cpp

#include "example.h"
#include <iostream>

// Private implementation
struct Example::Impl
{
    template <typename T>
    void PrintData(const T& data) const
    {
        std::cout << data << std::endl;
    }
};

// Note: using C++11 so "make_unique" is not available
Example::Example() : m_impl(new Impl()) {}
Example::~Example() = default;

// Forward the method call to the implementation
template <typename T>
void Example::PrintData(const T& data) const
{
    m_impl->PrintData(data);
}

// Don't want to have to explicitly state all types...
// this is not practical because "PrintData" should work for ANY printable type.

// Uncommenting the line below will allow this example to compile,
// but it will only compile when used with "int" data.
//template void Example::PrintData(const int& data) const;

main.cpp

#include "example.h"

int main()
{
    Example ex;
    ex.PrintData(42);
}

尝试编译它会失败并出现 undefined reference 错误:

g++ -std=c++11 -o main main.cpp example.cpp
/tmp/cc6IhZsx.o: In function `main':
main.cpp:(.text+0x2b): undefined reference to `void Example::PrintData<int>(int const&) const'
collect2: error: ld returned 1 exit status

这个例子在接口(interface)中提供了一个非常通用的函数,它应该适用于任何可打印类型。这带来了一个问题,因为在编译时之前,我们无法切实预测该函数将用于的所有类型。

我怎样才能得到像这样的函数来使用 PIMPL 习语(或其他一些创建“compiler firewall”的方法)

请注意,此示例是为了演示问题而故意设置的。在现实世界中,我有一些类在需要处理多种类型的接口(interface)中包含更复杂的模板函数。因此,我一直无法像 PIMPL 惯用语那样将这些类置于编译器防火墙后面。

我愿意考虑 PIMPL 习语的替代方案,如果存在的话,可以达到预期的效果。

最佳答案

如果您必须使用 PImpl 习惯用法并且必须支持任何类型,那么最好的办法就是类型删除行为。这允许您隐藏实现的部分,但不是全部。

对于简单的情况,std::function 或提议的 std::function_ref 可以工作:

#include <memory>
#include <ostream> // We must define some of the behavior in the header file.
#include <functional> // for std::function

class Example
{
public:
    Example();
    ~Example();

    // Generic template function in the interface
    template <typename T>
    void PrintData(const T& data) const;

private:
    struct Impl;
    std::unique_ptr<Impl> m_impl;

    // Some alternatives:
    // Some cost:
    void PrintDataImpl(const void* obj,
         const std::function<void(std::ostream&, const void*)>&) const;
    // Not yet standardized:
    void PrintDataImpl(const void* obj,
         std::function_ref<void(sstd::ostream&, const void*)>) const;
    // C-style:
    void PrintDataImpl(const void* obj,
         void(*fn)(std::ostream&, const void*)) const;
};

template <typename T>
void Example::PrintData(const T& data) const
{
    // std::function is expensive, but in this case, we fit in the small buffer
    // optimization as a lambda with no captures.
    // If we used `std::function_ref`, this would be cheaper.
    // If we used the C-style, the lambda cannot capture anything.
    PrintDataImpl(&data, [](std::ostream& out, const void* obj) {
        out << *static_cast<const T*>(obj);
    });
}


// In Example.cpp:
void Example::PrintDataImpl(const void* obj,
     const std::function<void(std::ostream&, const void*)>& fn) const
{
    // Call the function to apply the one piece of type-erased behavior.
    fn(std::cout, obj);
    std::cout << std::endl;
}

关于c++ - 将 PIMPL 习惯用法与成员函数模板一起使用(事先不知道所有可能的数据类型),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56499235/

相关文章:

c++ - 通过在 constexpr 构造函数中传递 N 在编译时创建大小为 N 的数组

c++ - 非虚推导: what do I really get from the compiler?

c++ - 从参数包中获取 typedef

c++ - 可移植的 c++ 对齐?

模板函数上的 C++ 模板类成员

c++ - 是否可以检查静态变量是否已在 C++ 中初始化?

c++ - dll 中的 std::unique_ptr pimpl 使用 Visual Studio 生成 C4251

c++ - pimpl 习语和模板类 friend

c++ - 回复 : Help with Boost Grammar

angular - NgStyle 返回 : ERROR Error: Cannot find a differ supporting object '{"background-color":"blue"}'