前言 最近寫一個任務隊列,可以支持存入返回值為void的任意函數對象。需要定義一個Task模板,來存儲函數對象以及參數。大致的實現如下: class Task { public: template <typename Func, typename... Args> Task(Func&& f, Ar ...
前言
最近寫一個任務隊列,可以支持存入返回值為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);