使用extern模板
使用std::unique_ptr<T>(其中T是不完整类型)的问题是unique_ptr需要能够为各种操作删除T的实例。类unique_ptr使用std::default_delete<T>来删除实例。因此,在一个理想的世界里,我们只会写
extern template class std::default_delete<T>;
防止std::default_delete<T>被实例化。然后,宣布
template class std::default_delete<T>;
在T完整的地方,将实例化模板。
这里的问题是default_delete实际上定义了不会被实例化的内联方法。所以,这个想法是行不通的。然而,我们可以解决这个问题。
首先,让我们定义一个不内联调用操作符的删除器。
/* --- opaque_ptr.hpp ------------------------------------------------------- */
#ifndef OPAQUE_PTR_HPP_
#define OPAQUE_PTR_HPP_
#include <memory>
template <typename T>
class opaque_delete {
public:
void operator() (T* ptr);
};
// Do not move this method into opaque_delete, or it will be inlined!
template <typename T>
void opaque_delete<T>::operator() (T* ptr) {
std::default_delete<T>()(ptr);
}
此外,为了便于使用,定义一个组合了unique_ptr和opaque_delete的类型opaque_ptr,类似于std::make_unique,我们定义了make_opaque。
/* --- opaque_ptr.hpp cont. ------------------------------------------------- */
template <typename T>
using opaque_ptr = std::unique_ptr<T, opaque_delete<T>>;
template<typename T, typename... Args>
inline opaque_ptr<T> make_opaque(Args&&... args)
{
return opaque_ptr<T>(new T(std::forward<Args>(args)...));
}
#endif
类型opaque_delete现在可以与extern模板构造一起使用。这里有一个例子。
/* --- foo.hpp -------------------------------------------------------------- */
#ifndef FOO_HPP_
#define FOO_HPP_
#include "opaque_ptr.hpp"
class Foo {
public:
Foo(int n);
void print();
private:
struct Impl;
opaque_ptr<Impl> m_ptr;
};
// Do not instantiate opaque_delete.
extern template class opaque_delete<Foo::Impl>;
#endif
因为我们阻止了opaque_delete被实例化,所以代码编译时不会出现错误。为了让链接器开心,我们在foo.cpp中实例化了opaque_delete。
/* --- foo.cpp -------------------------------------------------------------- */
#include "foo.hpp"
#include <iostream>
struct Foo::Impl {
int n;
};
// Force instantiation of opaque_delete.
template class opaque_delete<Foo::Impl>;
其余的方法可以按如下方式实现。
/* --- foo.cpp cont. -------------------------------------------------------- */
Foo::Foo(int n)
: m_ptr(new Impl)
{
m_ptr->n = n;
}
void Foo::print() {
std::cout << "n = " << m_ptr->n << std::endl;
}
这种解决方案的优点是,定义了opaque_delete之后,所需的样板代码相当小。