我正在尝试使用PIMPL惯用法来实现通用接口,该惯用接口需要一些模板成员函数来提供 ANY 类型的功能。问题是,如果不预先声明函数可能使用的每种可能的类型,我将找不到任何方法来支持将模板函数与PIMPL习惯用法结合使用。
查看相关问题:Pimpl Idiom with template member function
对该问题的公认答案建议明确定义将与模板一起使用的每种类型。这并不总是可行的。考虑以下示例:
示例。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);
}
尝试编译它会失败,并出现未定义的引用错误:
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
此示例在界面中提供了非常通用的功能,该功能应可用于任何可打印类型。这引起了一个问题,因为我们无法切实地预测直到编译时才会使用此函数的所有类型。
如何获得与PIMPL习惯用法(或其他创建“ compiler firewall”的方法)类似的功能
请注意,此示例故意用来说明问题。在现实世界中,我有一些类,它们在接口中包含更复杂的模板函数,这些模板函数需要在许多类型上使用。因此,我无法将这些类放在像PIMPL惯用语所提供的编译器防火墙后面。
我愿意考虑使用PIMPL惯用语的替代方法,如果存在此类替代方法,则可以达到预期的效果。
答案 0 :(得分:0)
如果必须使用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;
}