I try to use libcurl in a C program:
size_t getContainersCallback(const char *buff, size_t size, size_t buff_size, void *data) {
char newBuff[buff_size 1];
memset(newBuff, 0, buff_size 1);
memcpy(newBuff, buff, buff_size);
static_cast<string *>(data)->append(newBuff);
return size * buff_size;
}
int main() {
CURL *curl = curl_easy_init();
string responseBody{};
...
if (curl_easy_perform(curl) == CURLE_OK) {
json j = json::parse(responseBody.c_str());
...
}
curl_easy_cleanup(curl);
return 0;
}
But when the data obtained is not complete json, the function json::parse
will throw an exception, causing the pointer curl
to not be recycled.
The following method feels too stupid and forces me to write one more line of code:
try {
json j = json::parse(responseBody.c_str())
...
} catch (exception &e) {
curl_easy_cleanup(curl);
throw e;
}
curl_easy_cleanup(curl);
When using other C libraries, you should also encounter similar problems. Is there any other better way?
CodePudding user response:
You should use destructors to handle resource freeing. std::unique_ptr
is flexible enough that you can repurpose it:
// include curl, etc.
#include <memory>
struct CurlDeleter {
void operator()(CURL* p) const noexcept {
if (p) {
curl_easy_cleanup(p);
}
}
};
using CurlPtr = std::unique_ptr<CURL, CurlDeleter>;
int main() {
CurlPtr curl{curl_easy_init()};
// ...
if (curl_easy_perform(curl.get()) == CURLE_OK) {
json j = json::parse(responseBody.c_str());
// may throw, doesn't matter
}
// curl_easy_cleanup is called automatically
}
CodePudding user response:
try {
json j = json::parse(responseBody.c_str())
} catch (exception &e) {
curl_easy_cleanup(curl);
}
curl_easy_cleanup(curl);
I don't think you need curl_easy_cleanup
inside the catch, since you're recovering from the exception, then the function will be called twice.
This is not a specific issue with C
, ifcleanup you have an exception, there are cases where not everything is cleaned up correctly, and you have to be careful in those cases.
If you really want to have something that works well with exceptions, you need to either do how you started, sprinkle try
statements when needed.
You can write a wrapper around the curl
objects, which will handle cleanup through RAII.
Or you can use an existing wrapper such as http://www.curlpp.org/.
CodePudding user response:
The C way of handling this kind of cleanup is typically done through RAII pattern. Basically something like this:
class CurlWrapper
{
public:
CurlWrapper()
{
curl = curl_easy_init();
}
~CurlWrapper()
{
curl_easy_cleanup(curl);
curl = nullptr;
}
Curl* GetCurl() const
{
return curl;
}
private:
Curl* curl;
};
Then you would use it as follows:
{
if (curl_easy_perform(curl.GetCurl()) == CURLE_OK) {
json j = json::parse(responseBody.c_str());
...
}
}
Now when the CurlWrapper
goes out of scope (either due to function completing or unhandled exception, the destructor and hence the cleanup operation are called. This then works similarly to a finally
block in Java. Though in this case
CodePudding user response:
If there's only the one place, I'd stick with a simple try
/catch
, but if there are multiple places that need similar cleaning up, write a custom deleter and use it with unique_ptr
:
struct curl_ptr_freer {
void operator()(CURL *curlptr) {
curl_easy_cleanup (curlptr);
}
};
...
std::unique_ptr <CURL, curl_ptr_freer> curl = curl_easy_init();
unique_ptr
will take advantage of RAII to clean up when leaving scope. However, this replaces the original ugliness with new: anytime you need to use curl
as a parameter, you'll need to instead use curl.get()
to retrieve the raw pointer from the unique_ptr
.