functor
假想有一個函數的功能是將傳進來的參數加一之後回傳:
int add_one(int x){
return x + 1;
}
如果我們想把該函數的功能改為加二,一種方法是再定義一個同樣的函數,但是把加一改成加二:
int add_two(int x){
return x + 2;
}
雖然上面這種方法也能達到目的,但總顯得不夠簡潔。
另一種比較簡潔的方法則是定義一個functor
(即function object
)“類別”。包括三部份:
- 定義一個私有成員變數(用來當作"加數")
- 建構子接受一個參數,把剛定義的私有成員變數(即"加數")設為該值
- overload
()
這個運算子:其功能是把參數加上"加數"後回傳
class Adder{
private:
int addend; //加數
public:
Adder(int x) : addend(x) {}
int operator() (int augend) const {
return augend + addend;
}
};
如此一來,我們每定義一個Adder
類別的物件,就能得到為參數加一個特定"加數"的functor,而不必像上一個方法中為每一個"加數"都定義一個函數了:
#include <iostream>
// Driver code
int main()
{
int arr[] = {1, 2, 3};
//this is a functor that adds a number by 3
Adder myadder(3);
for(int e : arr){
std::cout << myadder(e) << " ";
}
std::cout << std::endl;
// 4 5 6
myadder = Adder(5);
for(int e : arr){
std::cout << myadder(e) << " ";
}
std::cout << std::endl;
// 6 7 8
}
有了上述概念後,現在再來看FunctionObject(即functor)的官方定義應該會比較好理解,來自C++ named requirements: FunctionObject:
A FunctionObject type is the type of an object
that can be used on the left of the function call operator.
即,FunctionObject(functor)是一種類型,對於該類型的一個物件f
,我們總是可以在它後面接上function call operator,()
,以此來調用函數。
在TensorRT/samples/common/buffers.h
中:
//! AllocFunc must be a functor that takes in (void** ptr, size_t size)
//! and returns bool. ptr is a pointer to where the allocated buffer address should be stored.
//! size is the amount of memory in bytes to allocate.
//! The boolean indicates whether or not the memory allocation was successful.
//! FreeFunc must be a functor that takes in (void* ptr) and returns void.
template <typename AllocFunc, typename FreeFunc>
class GenericBuffer
{
public:
GenericBuffer(size_t size, nvinfer1::DataType type)
: mSize(size)
, mCapacity(size)
, mType(type)
{
//調用allocFn的operator()函數來分配記憶體
if (!allocFn(&mBuffer, this->nbBytes()))
{
//failure to allocate storage
throw std::bad_alloc();
}
}
~GenericBuffer()
{
//調用freeFn的operator()函數來釋放記憶體
freeFn(mBuffer);
}
private:
//...
void* mBuffer;
//兩個function object
AllocFunc allocFn;
FreeFunc freeFn;
};
class DeviceAllocator
{
public:
//注意其參數是指標的指標void**
//overload ()這個operator
bool operator()(void** ptr, size_t size) const
{
return cudaMalloc(ptr, size) == cudaSuccess;
}
};
class DeviceFree
{
public:
void operator()(void* ptr) const
{
cudaFree(ptr);
}
};
class HostAllocator
{
public:
//ptr是指向指標的指標
bool operator()(void** ptr, size_t size) const
{
//分配一塊記憶體,並將其位置指定給ptr指向的指標
*ptr = malloc(size);
//確認拿到的記憶體位置非空
return *ptr != nullptr;
}
};
class HostFree
{
public:
void operator()(void* ptr) const
{
free(ptr);
}
};
using DeviceBuffer = GenericBuffer<DeviceAllocator, DeviceFree>;
using HostBuffer = GenericBuffer<HostAllocator, HostFree>;
首先定義 DeviceAllocator
, DeviceFree
, HostAllocator
及 HostFree
等四個functor。
其次在GenericBuffer
中定義了兩個相對應的物件allocFn
及freeFn
。
最後因為()
已經被overload過了,所以可以透過 allocFn(&mBuffer, this->nbBytes())
及 freeFn(mBuffer)
來調用。
根據Why use functors over functions?,比起function pointer,使用functor有以下幾個好處:
- parameterisation:可以簡單地透過參數來讓函數完成不同的功能
- statefulness:調用functor可以改變functor本身的狀態,影響下回調用的結果
- separation of concerns:在計算平均值的例子中,將遍歷容器的邏輯及計算平均值的邏輯分開
- performance:functor能較容易地被編譯器"inline",這能大幅地提升運行速度
但在這個例子中,分配及釋放記憶體的函數都不需要parameterisation,亦非statefulness。那麼為何還要用functor而非function pointer呢?
唯一比較能說服筆者的說法來自Function or functor as template parameter? (If it does not need a state):
If you opted for a function pointer,
then the function being defined would
impose a requirement on the users, reducing flexibility.
也就是說,為了程序的彈性考量(日後GenericBuffer
可能會需要parameterisation或stateful的函數),所以才採用functor,而非使用靈活性不足的function pointer。
參考連結
Why use functors over functions?
Function or functor as template parameter? (If it does not need a state)