This introduces a little pattern I discovered the other day, to enhance encapsulation around safe resource access.
Maybe we have some resource which we want to apply Resource Acquisition is Initialization (RAII) to. For any readers not familiar with RAII that may be something like an open file, or a mutex or some other resource for which we care about it being released by the program once the program has finished accessing it. It order to manage this in C++ we typically create a class type which acquires the resource in its constructor, allows access to the acquired resource via public methods and releases the resource in the destructor. The C++ language guarantees that the destructor of such an object will be called when its enclosing scope terminates.
Often the use of the RAII class will be couched in value semantics so the RAII object lives in a scope on the stack.
In order to take advantage of RAII we will sometimes want to get some resource off another interface. Often I would find myself in these situations, adding to the API enough public access to get the parameters to call the RAII types constructor. In many cases this is not desirable, e.g when the access to these members of the API is not otherwise needed.
Thanks to the introduction of C++11 move semantics we can do without publishing these details in the API and coupled with this enforce the use of RAII. Of course this was possible before C++11 but with move semantics its the natural thing to do.
So when using the API we nicely encapsulate the parameters and end up using natural value semantic RAII.
Maybe we have some resource which we want to apply Resource Acquisition is Initialization (RAII) to. For any readers not familiar with RAII that may be something like an open file, or a mutex or some other resource for which we care about it being released by the program once the program has finished accessing it. It order to manage this in C++ we typically create a class type which acquires the resource in its constructor, allows access to the acquired resource via public methods and releases the resource in the destructor. The C++ language guarantees that the destructor of such an object will be called when its enclosing scope terminates.
class RAIIType {
RAIIType(int param1, int param2, const char* param3) : resource(nullptr) {
// Call external library function to access resource.
resource = acquireTheResource(param1, param2, param3);
}
~RAIIType() {
// Call external library function to free resource.
freeTheResource(resource);
}
void useTheResource(int p1, int p2, int p3) {
// Call external library function to use the resource.
useTheResource(resource, p1, p2, p3);
}
private:
// We hide the copy constructor and copy assignment operator
// so that these can't accidentally be called.
RAIIType(const RAIIType& other);
RAIIType& operator =(const RAIIType& other);
private:
void* resource;
};
Often the use of the RAII class will be couched in value semantics so the RAII object lives in a scope on the stack.
In order to take advantage of RAII we will sometimes want to get some resource off another interface. Often I would find myself in these situations, adding to the API enough public access to get the parameters to call the RAII types constructor. In many cases this is not desirable, e.g when the access to these members of the API is not otherwise needed.
class APIType {
public:
// We would prefer not to have these accessor methods as
// they provide internal details of the type to users.
int getParam1() const { return param1; }
int getParam2() const { return param2; }
const std::string& getParam3() const { return param3; }
private:
int param1;
int param2;
std::string param3;
};
void someFunction(const APIType& api) {
RAIIType type(api.getParam1(), api.getParam2(), api.getParam3().c_str());
// Do some other stuff, potentially throw an exception here.
type.useTheResource(1, 2, 3);
}
Thanks to the introduction of C++11 move semantics we can do without publishing these details in the API and coupled with this enforce the use of RAII. Of course this was possible before C++11 but with move semantics its the natural thing to do.
class RAIIType {
RAIIType(int param1, int param2, const char* param3) {
// Call external library function to access resource.
resource = acquireTheResource(param1, param2, param3);
}
~RAIIType() {
if (resource) {
// Call external library function to free resource.
freeTheResource(resource);
}
}
// This is the move constructor, other is moved into this.
RAIIType(RAIIType&& other) {
*this = other; // Calls operator=
}
// This is the move assignment operator, other is moved into this.
RAIIType& operator =(RAIIType&& other) {
resource = other.resource;
other.resource = nullptr;
return *this;
}
// We delete the copy constructor and copy assignment operator
// so that these can't accidentally be called.
RAIIType(const RAIIType& other) = delete;
RAIIType& operator =(const RAIIType& other) = delete;
void useTheResource(int p1, int p2, int p3) {
// Call external library function to use the resource.
useTheResource(resource, p1, p2, p3);
}
private:
void* resource = nullptr;
};
class APIType {
public:
// This must construct the RAIIType in the external scope or move return
// it due to Return Value Optimization.
RAIIType openRAIIResource() const {
return RAIIType(param1, param2, param3.c_str());
}
private:
int param1;
int param2;
std::string param3;
};
So when using the API we nicely encapsulate the parameters and end up using natural value semantic RAII.
void someFunction(const APIType& api) {
RAIIType type = api.openRAIIResource();
// Do some other stuff, potentially throw an exception here.
type.useTheResource(1, 2, 3);
}
Comments
Post a Comment