前言
最近写一个任务队列,可以支持存入返回值为void的任意函数对象。需要定义一个Task模板,来存储函数对象以及参数。大致的实现如下:
class Task
{
public:
template <typename Func, typename... Args>
Task(Func&& f, Args &&...args)
: func_(std::bind(std::forward<Func>(f), std::forward<Args>(args)...)) {}
void operator()()
{
func_();
}
private:
std::function<void()> func_;
};
其中构造函数是一个函数模板,可以在编译的时候,根据传入的函数对象和参数,绑定生成std::function,存储在func_中。
支持形如
auto f1 = [](int i, int j)
{
std::cout << i << j;
};
auto f2 = [](int i, double j)
{
std::cout << i << j;
};
Task t(f1, 5, 6);
Task t2(f2, 1, 2.3);
复制栈溢出
但下面这个普通的“拷贝”,linux编译正常,用clang编译器的话会造成栈溢出。
auto t3 = t;
用vs看调用堆栈,发现一直在执行Task::<Task&>(Task & f)--->bind---- > binder等等,一直在执行构造函数。
我尝试自定义拷贝构造,并在其中输出。但发现拷贝函数根本没有执行,而是在反复执行函数模板。
Task(const Task & other) :func_(other.func_)
{
std::cout << "copy ctor";
}
为什么没有调用拷贝构造
仔细研究发现调用堆栈,发现调用的是Task::<Task&>(Task& f),这并不是拷贝构造,这是函数模板自动生成的。
在https://cppinsights.io/这个网址,提供了编译结果,可以看模板生成了那些函数。
#include <functional>
#include<iostream>
class Task
{
public:
template<typename Func, typename ... Args>
inline Task(Func&& f, Args &&... args)
: func_{ std::bind(std::forward<Func>(f), std::forward<Args>(args)...) }
{
}
/* First instantiated from: insights.cpp:37 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline Task<__lambda_29_13&, int, int>(__lambda_29_13& f, int&& __args1, int&& __args2)
: func_{ std::function<void()>(std::bind(std::forward<__lambda_29_13&>(f), std::forward<int>(__args1), std::forward<int>(__args2))) }
{
}
#endif
/* First instantiated from: insights.cpp:38 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline Task<__lambda_33_13&, int, double>(__lambda_33_13& f, int&& __args1, double&& __args2)
: func_{ std::function<void()>(std::bind(std::forward<__lambda_33_13&>(f), std::forward<int>(__args1), std::forward<double>(__args2))) }
{
}
#endif
/* First instantiated from: insights.cpp:39 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline Task<Task&>(Task& f)
: func_{ std::function<void()>(std::bind(std::forward<Task&>(f))) }
{
}
#endif
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline Task<const Task&>(const Task& f);
#endif
/* First instantiated from: functional:558 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline Task<Task>(Task&& f)
: func_{ std::function<void()>(std::bind(std::forward<Task>(f))) }
{
}
#endif
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline Task<const std::_Bind<Task()>&>(const std::_Bind<Task()>& f);
#endif
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline Task<std::_Bind<Task()> >(std::_Bind<Task()>&& f);
#endif
inline void operator()()
{
this->func_.operator()();
}
inline Task(const Task& other)
: func_{ std::function<void()>(other.func_) }
{
std::operator<<(std::cout, "copy ctor");
}
private:
std::function<void()> func_;
public:
// inline ~Task() noexcept = default;
};
int main()
{
class __lambda_29_13
{
public:
inline /*constexpr */ void operator()(int i, int j) const
{
std::cout.operator<<(i).operator<<(j);
}
using retType_29_13 = void (*)(int, int);
inline constexpr operator retType_29_13 () const noexcept
{
return __invoke;
};
private:
static inline /*constexpr */ void __invoke(int i, int j)
{
__lambda_29_13{}.operator()(i, j);
}
public:
// inline /*constexpr */ __lambda_29_13 & operator=(const __lambda_29_13 &) /* noexcept */ = delete;
// inline /*constexpr */ __lambda_29_13(const __lambda_29_13 &) noexcept = default;
// inline /*constexpr */ __lambda_29_13(__lambda_29_13 &&) noexcept = default;
};
__lambda_29_13 f1 = __lambda_29_13{};
class __lambda_33_13
{
public:
inline /*constexpr */ void operator()(int i, double j) const
{
std::cout.operator<<(i).operator<<(j);
}
using retType_33_13 = void (*)(int, double);
inline constexpr operator retType_33_13 () const noexcept
{
return __invoke;
};
private:
static inline /*constexpr */ void __invoke(int i, double j)
{
__lambda_33_13{}.operator()(i, j);
}
public:
// inline /*constexpr */ __lambda_33_13 & operator=(const __lambda_33_13 &) /* noexcept */ = delete;
// inline /*constexpr */ __lambda_33_13(const __lambda_33_13 &) noexcept = default;
// inline /*constexpr */ __lambda_33_13(__lambda_33_13 &&) noexcept = default;
};
__lambda_33_13 f2 = __lambda_33_13{};
Task t = Task(f1, 5, 6);
Task t2 = Task(f2, 1, 2.2999999999999998);
Task t3 = Task(t);
return 0;
}
可以看到最后t3是这么生成的。
Task t3 = Task(t);
而Task类中有两个形如这样的函数;
//这个是拷贝构造函数
inline Task(const Task& other)
: func_{ std::function<void()>(other.func_) }
{
std::operator<<(std::cout, "copy ctor");
}
//这个是函数模板生成的一个构造函数,参数为Task &f。
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline Task<Task&>(Task& f)
: func_{ std::function<void()>(std::bind(std::forward<Task&>(f))) }
{
}
#endif
这下明白为什么没有调用拷贝构造了,原来Task t3 = Task(t)中,等号右边的Task(t)并不是拷贝构造函数。因为t是非常量左值,所以编译器优先匹配模板函数的参数(Task & )。
为什么会栈溢出
接下来是为什么这个模板函数会递归构造,直至栈溢出。
观察这个模板函数,发现其形参中有std::bind。而这个函数会复制拷贝传入参数(这里就是Task)。而复制Task并不会调用构造函数,而是调用这个函数模板,因此,一直递归调用直至栈溢出。
解决
既然非常量左值匹配不上拷贝构造,那就把返回值转换成常量左值, 改成下面这种形式。就没问题了
auto t3 = static_cast<Task&>(t);