C++ RAII(Resource Acquisition Is Initialization)
RAII(Resource Acquisition Is Initialization)
RAII,即"資源獲取即初始化"。或許換個稱呼會比較好理解:Scope-Bound Resource Management,暫譯"與變數可視範圍綁定的資源管理"。
在C++中,通常需要程序員手動地進行內存管理:
- 在使用前需要用
new
來申請一塊內存 - 使用結束後,用
delete
來釋放內存
如果我們錯誤地使用那塊內存,導致異常拋出,那麼後面的delete
就不會被執行。這將會導致memory leak的問題。詳見下例:
void set(int* resource, int size, int index, int value){
if(index < 0 || index > size){
throw std::out_of_range("invalid index!");
}
resource[index] = value;
}
int main(){
int* resource;
int size = 100;
resource = new int[size];
std::cout << "resource initialized" << std::endl;
std::cout << "set 100th element..." << std::endl;
set(resource, size, 100, 0);
std::cout << "set 101th element..." << std::endl;
set(resource, size, 101, 0);
delete resource; //不會被執行
return 0;
}
Scope-Bound Resource Management(RAII)的概念則是:
The 'Scope-bound' aspect means that the lifetime of the object
is bound to the scope of a variable,
so when the variable goes out of scope
then the destructor will release the resource.
意即資源的生命週期與變數的可視範圍綁定,每當變數落在可視範圍之外時,destructor就會自動地被調用,用以釋放資源。
其具體做法為:
- 在類別的constructor中申請內存
- 在類別的destructor中釋放內存
如此一來,每當程序執行出錯時,變數就會落到可視範圍之外,接著destructor會自動被調用,從而能正確地將內存給釋放出去。
詳見下例:
class ResourceManager{
public:
ResourceManager(): size(0), resource(nullptr){
std::cout << "resource initialized" << std::endl;
}
ResourceManager(int size): size(size){
resource = new int[size];
std::cout << "resource initialized" << std::endl;
}
~ResourceManager(){
delete resource;
std::cout << "resource released" << std::endl;
}
void set(int index, int value){
if(index < 0 || index > size){
throw std::out_of_range("invalid index!");
}
resource[index] = value;
}
int get(int index){
if(index < 0 || index > size){
throw std::out_of_range("invalid index!");
}
return resource[index];
}
private:
int* resource;
int size;
};
int main(){
try{
ResourceManager rm(100);
std::cout << "set 100th element..." << std::endl;
rm.set(100, 0);
std::cout << "set 101th element..." << std::endl;
rm.set(101, 0);
//destructor will be called even if there is an exception
}catch(...){
throw;
}
return 0;
}
完整代碼放在cpp-code-snippets/RAII.cpp。
在TensorRT/samples/common/buffers.h
中,GenericBuffer
及BufferManager
皆為RAII class,以下是它們的constructor及destructor:
template <typename AllocFunc, typename FreeFunc>
class GenericBuffer
{
public:
//!
//! \brief Construct an empty buffer.
//!
GenericBuffer(nvinfer1::DataType type = nvinfer1::DataType::kFLOAT)
: mSize(0)
, mCapacity(0)
, mType(type)
, mBuffer(nullptr)
{
}
//!
//! \brief Construct a buffer with the specified allocation size in bytes.
//!
GenericBuffer(size_t size, nvinfer1::DataType type)
: mSize(size)
, mCapacity(size)
, mType(type)
{
if (!allocFn(&mBuffer, this->nbBytes()))
{
throw std::bad_alloc();
}
}
GenericBuffer(GenericBuffer&& buf)
: mSize(buf.mSize)
, mCapacity(buf.mCapacity)
, mType(buf.mType)
, mBuffer(buf.mBuffer)
{
buf.mSize = 0;
buf.mCapacity = 0;
buf.mType = nvinfer1::DataType::kFLOAT;
buf.mBuffer = nullptr;
}
//...
~GenericBuffer()
{
freeFn(mBuffer);
}
private:
size_t mSize{0}, mCapacity{0};
nvinfer1::DataType mType;
void* mBuffer;
AllocFunc allocFn;
FreeFunc freeFn;
};
可以看到,在GenericBuffer
的constructor中申請了mBuffer
這塊內存,在destructor中則釋放了它。
以下是BufferManager
的constructor及destructor:
using DeviceBuffer = GenericBuffer<DeviceAllocator, DeviceFree>;
using HostBuffer = GenericBuffer<HostAllocator, HostFree>;
class BufferManager
{
public:
static const size_t kINVALID_SIZE_VALUE = ~size_t(0);
//!
//! \brief Create a BufferManager for handling buffer interactions with engine.
//!
BufferManager(std::shared_ptr<nvinfer1::ICudaEngine> engine, const int& batchSize,
const nvinfer1::IExecutionContext* context = nullptr)
: mEngine(engine)
, mBatchSize(batchSize)
{
// Create host and device buffers
for (int i = 0; i < mEngine->getNbBindings(); i++)
{
//...
manBuf->deviceBuffer = DeviceBuffer(vol, type);
manBuf->hostBuffer = HostBuffer(vol, type);
//...
}
}
//...
~BufferManager() = default;
private:
//...
std::shared_ptr<nvinfer1::ICudaEngine> mEngine; //!< The pointer to the engine
int mBatchSize; //!< The batch size
std::vector<std::unique_ptr<ManagedBuffer>> mManagedBuffers; //!< The vector of pointers to managed buffers
std::vector<void*> mDeviceBindings; //!< The vector of device buffers needed for engine execution
};
可以看到,在BufferManager
的constructor中調用了DeviceBuffer
及HostBuffer
的建構子。而這兩個類別都屬於GenericBuffer
類別的特例,所以實際上仍然是在建構子中申請內存。
BufferManager
的成員變數mEngine
是一個smart pointer;mMangedBuffers
是由smart pointer構成的vector;mBatchSize
則屬於C++的basic type。根據Smart Pointers in C++,C++ Destructors with Vectors, Pointers,及Do I need to delete basic data types in a destructor? C++,這三種情況都不需要顯式地調用delete
來釋放內存。(那mDeviceBindings
呢?)所以這裡使用由編譯器自動生成的destructor也沒關係。
參考連結
What is meant by Resource Acquisition is Initialization (RAII)?
C++ Destructors with Vectors, Pointers,