引子
下面给出一个单例模板共享实现:
template <typename T>
class MySingletonRef {
public:
// new 创建
static T& GetInstance() {
auto ret = s_instance.load();
if (ret == nullptr) {
std::lock_guard<std::mutex> _l(s_mutex);
ret = s_instance.load();
if (ret == nullptr) {
ret = new T();
s_instance.store(ret);
}
}
return *ret;
}
// 共享创建
static std::shared_ptr<T>& GetSharedInstance() {
std::call_once(singletonFlag, [&] {
s_singleton_ = std::make_shared<T>();
});
return s_singleton_;
}
// new 释放
static void ReleaseInstance() {
auto tmp = s_instance.load();
if (tmp != nullptr) {
std::lock_guard<std::mutex> _l(s_mutex);
tmp = s_instance.load();
if (tmp != nullptr) {
delete tmp;
s_instance.store(nullptr);
}
}
}
protected:
MySingletonRef() = default;
virtual ~MySingletonRef() = default;
MySingletonRef(const MySingletonRef&) = delete;
MySingletonRef& operator=(const MySingletonRef&) = delete;
}
下面是项目中客户简单使用的例子:
class LogReporter : public MySingletonRef<LogReporter>, ...{
public:
friend class MySingletonRef<LogReporter>;
void AddLog(Json::Value log);
private:
LogReporter();
~LogReporter() override;
LogReporter(const LogReporter&) = delete;
LogReporter& operator=(const LogReporter&) = delete;
...
};
问题
以上代码中共享创建的获取接口GetSharedInstance()会报编译错误(大意是make_shared法访问T的构造函数,这个报错很好理解,也一目了然:因为std::make_shared不是T的友元),那问题怎么解决呢?
难道把std::make_shared声明为Widget的friend就能解决问题吗?
template <typename T, typename ...Args>
friend std::shared_ptr<T> std::make_shared(Args &&...);
并不能,由于标准库函数之间的层层嵌套调用,对于T构造函数的调用可能根本不是发生在make_shared中。如果你想寻根究底地找下去,那自然是可以的,但最终你会获得一份移植性低的代码。(事实上,在C++11下和在C++20下这个调用就发生在不同的函数中。)
解决思路往往是比较简单的,如下代码:wrapper语法糖绕过语法规则。
template <typename T>
class MySingletonRef {
public:
...
// 共享创建
static std::shared_ptr<T>& GetSharedInstance() {
std::call_once(singletonFlag, [&] {
struct make_shared_helper : public T {
make_shared_helper() : T() {}
};
s_singleton_ = std::make_shared<make_shared_helper>();
});
return s_singleton_;
}
...
}
总结
现状:类MySingletonRef可以访问T的构造,make_shared不能访问T的构造。
解决:包装类绕过编译器。make_shared_helper继承自T,make_shared_helper定义在了MySingletonRef类里面,所以可以访问MySingletonRef的构造,也就间接的可以访问T的构造;同时因为make_shared_helper的构造是公有的,make_shared可以访问,也就间接的可以访问T的构造。