spdlog是一个c++实现的日志库,代码中大量使用了c++11的特性,并且只需要头文件就可以使用,十分值得使用和研究。

下面这段代码是spdlog中的一个工厂函数,用来创建一个新的logger,里面用到了c++11之后才支持的完美转发。

1
2
3
4
5
6
7
8
template<typename Sink, typename... SinkArgs>
static std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&... args)
{
auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...);
auto new_logger = std::make_shared<spdlog::logger>(std::move(logger_name), std::move(sink));
details::registry::instance().initialize_logger(new_logger);
return new_logger;
}

SinkArgs这个可变长度的模板参数是完美转发的目标,定义成&&,右值引用。std::make_shared会在构造Sink对象时将SinkArgs传递给Sink的构造函数,SinkArgs需要使用std::forward传递,这样就能将参数原原本本的传递给Sink的构造函数,当Sink有不同的构造函数时还能自动匹配。

我们来写代码验证一下。

首先写一个有多种构造函数的类Data,每个构造函数里都不干啥,只是打印一条信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Data
{
public:
Data()
{
printf("Data()\n");
}
Data(int a)
{
printf("Data(int a)\n");
}
Data(int a, float b)
{
printf("Data(int a, float b)\n");
}
~Data()
{
printf("~Data()\n");
}
};

然后我们为Data写一个工厂函数,像下面这个样子,也比较简单,只是创建一个指针再返回出来。

1
2
3
4
5
template <typename T, typename ...Args>
std::shared_ptr<T> CreateData(Args &&...args)
{
return std::make_shared<T>(std::forward<Args>(args)...);
}

测试代码如下。

1
2
3
4
5
6
7
int main(int argc, const char *argv[])
{
auto a = CreateData<Data>();
auto b = CreateData<Data>(1);
auto c = CreateData<Data>(1,1.5);
return 0;
}

运行之后输出如下,就和预想的一样。

1
2
3
4
5
6
Data()
Data(int a)
Data(int a, float b)
~Data()
~Data()
~Data()

大家可能注意到,CreateData返回的并不是裸指针,而是make_shared出来的shared_ptr,下面是make_shared的代码,有没有觉得很眼熟呢?

1
2
3
4
5
6
template<typename _Tp, typename... _Args>
inline shared_ptr<_Tp> make_shared(_Args&&... __args)
{
typedef typename std::remove_const<_Tp>::type _Tp_nc;
return std::allocate_shared<_Tp>(std::allocator<_Tp_nc>(), std::forward<_Args>(__args)...);
}

没错,make_shared在为shared_ptr构建对象时也用到了完美转发,如果我们再给Data包装一个具有引用计数功能的管理类,就很像是shared_ptr的实现了。

参考链接:https://zhuanlan.zhihu.com/p/275075782