- 函數(shù)對(duì)象用于給模板提供一些可定制行為
#include <iostream>
#include <vector>
template<typename F>
void forUpTo(int n, F f)
{
for (int i = 0; i < n; ++i) f(i);
}
void print(int i)
{
std::cout << i << ' ';
}
int main()
{
std::vector<int> v;
forUpTo(5, [&v] (int i) { v.push_back(i); });
forUpTo(5, print); // prints 0 1 2 3 4
}
- forUpTo()可用于任何函數(shù)對(duì)象计贰,每次使用forUpTo()都將產(chǎn)生一個(gè)不同的實(shí)例化,這個(gè)模板很小授瘦,但如果很大則實(shí)例化的代碼也會(huì)很大闯第。一個(gè)限制代碼增長(zhǎng)的方法是將其改為無需實(shí)例化的非模板
void forUpTo(int n, void (*f)(int))
{
for (int i = 0; i < n; ++i) f(i);
}
- 但這樣就不允許接受lambda驼侠,因?yàn)閘ambda不能轉(zhuǎn)為函數(shù)指針
forUpTo(5, [&v] (int i) { v.push_back(i); }); // 錯(cuò)誤:lambda不能轉(zhuǎn)為void(*)(int)
#include <functional>
void forUpTo(int n, std::function<void(int)> f)
{
for (int i = 0; i < n; ++i) f(i);
}
- 這個(gè)實(shí)現(xiàn)提供了一些靜態(tài)多態(tài)方面的特點(diǎn)权埠,它能處理函數(shù)對(duì)象,但本身只是單個(gè)實(shí)現(xiàn)的非模板函數(shù)袍暴。它使用類型擦除(type erasure)的技術(shù)做到這點(diǎn)些侍,即在編譯期去掉不需要關(guān)心的原有類型(與實(shí)參推斷相反),類型擦除橋接了靜態(tài)多態(tài)與動(dòng)態(tài)多態(tài)的溝壑
-
std::function是一個(gè)廣義的函數(shù)指針形式政模,不同于函數(shù)指針的是娩梨,它能存儲(chǔ)一個(gè)lambda或其他任何帶有合適的operator()的函數(shù)對(duì)象。下面實(shí)現(xiàn)一個(gè)能替代std::function的類模板
#include <iostream>
#include <vector>
#include <type_traits>
template<typename R, typename... Args>
class B { // 橋接口:負(fù)責(zé)函數(shù)對(duì)象的所有權(quán)和操作
public: // 實(shí)現(xiàn)為抽象基類览徒,作為類模板A動(dòng)態(tài)多態(tài)的基礎(chǔ)
virtual ~B() {}
virtual B* clone() const = 0;
virtual R invoke(Args... args) const = 0;
};
template<typename F, typename R, typename... Args>
class X : public B<R, Args...> { // 抽象基類的實(shí)現(xiàn)
private:
F f; // 參數(shù)化存儲(chǔ)的函數(shù)對(duì)象類型狈定,以實(shí)現(xiàn)類型擦除
public:
template<typename T>
X(T&& f) : f(std::forward<T>(f)) {}
virtual X* clone() const override
{
return new X(f);
}
virtual R invoke(Args... args) const override
{
return f(std::forward<Args>(args)...);
}
};
// 原始模板
template<typename Signature>
class A;
// 偏特化
template<typename R, typename... Args>
class A<R(Args...)> {
private:
B<R, Args...>* bridge; // 該指針負(fù)責(zé)管理函數(shù)對(duì)象
public:
A() : bridge(nullptr) {}
A(const A& other) : bridge(nullptr)
{
if (other.bridge)
{
bridge = other.bridge->clone();
}
}
A(A& other) : A(static_cast<const A&>(other)) {}
A(A&& other) noexcept : bridge(other.bridge)
{
other.bridge = nullptr;
}
template<typename F>
A(F&& f) : bridge(nullptr) // 從任意函數(shù)對(duì)象構(gòu)造
{
using Functor = std::decay_t<F>;
using Bridge = X<Functor, R, Args...>; // X的實(shí)例化存儲(chǔ)一個(gè)函數(shù)對(duì)象副本
bridge = new Bridge(std::forward<F>(f)); // 派生類到基類的轉(zhuǎn)換,F(xiàn)丟失习蓬,類型擦除
}
A& operator=(const A& other)
{
A tmp(other);
swap(*this, tmp);
return *this;
}
A& operator=(A&& other) noexcept
{
delete bridge;
bridge = other.bridge;
other.bridge = nullptr;
return *this;
}
template<typename F>
A& operator=(F&& f)
{
A tmp(std::forward<F>(f));
swap(*this, tmp);
return *this;
}
~A() { delete bridge; }
friend void swap(A& fp1, A& fp2) noexcept
{
std::swap(fp1.bridge, fp2.bridge);
}
explicit operator bool() const
{
return bridge == nullptr;
}
R operator()(Args... args) const
{
return bridge->invoke(std::forward<Args>(args)...);
}
};
void forUpTo(int n, A<void(int)> f)
{
for (int i = 0; i < n; ++i) f(i);
}
void print(int i)
{
std::cout << i << ' ';
}
int main()
{
std::vector<int> v;
forUpTo(5, [&v] (int i) { v.push_back(i); });
forUpTo(5, print);
}
- 上述A模板還不支持函數(shù)指針提供的一個(gè)操作:測(cè)試是否兩個(gè)A對(duì)象將調(diào)用相同的函數(shù)纽什。這需要橋接口B提供一個(gè)equals操作
virtual bool equals(const B* fb) const = 0;
virtual bool equals(const B<R, Args...>* fb) const override
{
if (auto specFb = dynamic_cast<const X*> (fb))
{
return f == specFb->f; // 要求f有operator==
}
return false;
}
- 最終為A實(shí)現(xiàn)
operator==
friend bool operator==(const A& f1, const A& f2) {
if (!f1 || !f2)
{
return !f1 && !f2;
}
return f1.bridge->equals(f2.bridge); // equals要求operator==
}
friend bool operator!=(const A& f1, const A& f2)
{
return !(f1 == f2);
}
- 這個(gè)實(shí)現(xiàn)還有一個(gè)問題:如果A用沒有
operator==
的函數(shù)對(duì)象賦值或初始化,編譯將報(bào)錯(cuò)躲叼,即使A的operator==
還沒被使用芦缰。這個(gè)問題來源于類型擦除:一旦A被賦值或初始化,就丟失了函數(shù)對(duì)象的類型(派生類到基類的轉(zhuǎn)換)枫慷,這就要求在實(shí)例化前得知類型信息让蕾,這個(gè)信息包括對(duì)一個(gè)函數(shù)對(duì)象的operator==
的調(diào)用浪规。為此可以用SFINAE-based traits檢查operator==
是否可用
template<typename T>
class IsEqualityComparable {
private:
static void* conv(bool);
template<typename U>
static std::true_type test(
decltype(conv(std::declval<const U&>() == std::declval<const U&>())),
decltype(conv(!(std::declval<const U&>() == std::declval<const U&>())))
);
template<typename U>
static std::false_type test(...);
public:
static constexpr bool value = decltype(test<T>(nullptr, nullptr))::value;
};
// 構(gòu)造一個(gè)TryEquals在調(diào)用類型沒有合適的==時(shí)拋出異常
template<typename T, bool EqComparable = IsEqualityComparable<T>::value>
struct TryEquals {
static bool equals(const T& x1, const T& x2)
{
return x1 == x2;
}
};
class NotEqualityComparable : public std::exception {};
template<typename T>
struct TryEquals<T, false> {
static bool equals(const T& x1, const T& x2)
{
throw NotEqualityComparable();
}
};
- 在X中實(shí)現(xiàn)equals時(shí),使用TryEquals替代
operator==
即可達(dá)到同樣的目的
virtual bool equals(const B<R, Args...>* fb) const override
{
if (auto specFb = dynamic_cast<const X*> (fb))
{
return TryEquals<F>::equals(f, specFb->f);
}
return false;
}
性能考慮
- 類型擦除同時(shí)提供了部分靜態(tài)多態(tài)和動(dòng)態(tài)多態(tài)的優(yōu)點(diǎn)探孝。使用類型擦除的泛型代碼的性能更接近于動(dòng)態(tài)多態(tài)笋婿,因?yàn)閮烧叨纪ㄟ^虛函數(shù)使用動(dòng)態(tài)分派,因此可能丟失一些靜態(tài)多態(tài)的傳統(tǒng)優(yōu)點(diǎn)顿颅,如編譯器內(nèi)聯(lián)調(diào)用的能力缸濒。雖然這種性能損失能否感知依賴于應(yīng)用程序,但通常很容易判斷粱腻”优洌考慮相對(duì)虛函數(shù)調(diào)用開銷需要執(zhí)行多少工作:如果兩者接近,類型擦除可能比靜態(tài)多態(tài)慢得多绍些,反之如果函數(shù)調(diào)用執(zhí)行大量工作捞慌,如查詢數(shù)據(jù)庫(kù)、排序容器或更新用戶接口柬批,類型擦除的開銷就算不上多大