Boost Your C++ Memory Management Skills: A Gentle Introduction to boost::intrusive_ptr
An intrusive_ptr
is a type of smart pointer that uses reference counting to manage the lifetime of dynamically allocated objects. Unlike shared_ptr
, intrusive_ptr
keeps the reference count directly within the managed object, rather than using a separate allocation to store the count. This makes intrusive_ptr
more efficient in terms of memory usage and performance, but requires a different approach to implementing the reference counting mechanism.
Here’s an example of how to use intrusive_ptr
in C++:
#include <boost/intrusive_ptr.hpp>
#include <iostream>
class MyClass {
public:
MyClass() : ref_count_(0) {}
friend void intrusive_ptr_add_ref(MyClass* p) { ++p->ref_count_; }
friend void intrusive_ptr_release(MyClass* p) {
if (--p->ref_count_ == 0) delete p;
}
void Print() { std::cout << "Hello, world!" << std::endl; }
private:
int ref_count_;
};
int main() {
boost::intrusive_ptr<MyClass> ptr(new MyClass());
ptr->Print();
return 0;
}
In this example, we define a class MyClass
that has a reference count, and two friend functions intrusive_ptr_add_ref
and intrusive_ptr_release
that implement the reference counting mechanism. These functions are used by the intrusive_ptr
to manage the lifetime of MyClass
objects.
In the main
function, we create an instance of MyClass
and wrap it in an intrusive_ptr
. The reference count is automatically incremented when the intrusive_ptr
is created, and decremented when it goes out of scope. If the reference count reaches zero, the intrusive_ptr_release
function deletes the MyClass
object.
We compare the performance of intrusive_ptr
and shared_ptr
by copying the smart pointers for one million times (which increases the reference count). For fairness, we also use std::atomic
for reference count as in the std::shared_ptr
to ensure thread safety.
#include <boost/intrusive_ptr.hpp>
#include <memory>
#include <chrono>
#include <atomic>
#include <iostream>
// Object for shared_ptr
struct TestObject {
int data;
TestObject(int d) : data(d) {}
};
// Object for intrusive_ptr
struct TestObjectIntrusive: public TestObject {
TestObjectIntrusive(int d): TestObject(d) {}
friend void intrusive_ptr_add_ref(TestObjectIntrusive* p) {
++p->ref_cnt;
}
friend void intrusive_ptr_release(TestObjectIntrusive* p) {
if (--p->ref_cnt == 0) {
delete p;
}
}
std::atomic<std::size_t> ref_cnt;
};
// Helper function to measure the time taken for a block of code
template <typename Func>
double measureTime(Func func) {
auto start = std::chrono::steady_clock::now();
func();
auto end = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
return duration.count() / 1000000.0;
}
int main() {
constexpr auto N = 1000000;
// Measure time taken to shared_ptr copy ctor
auto timeTakenSharedPtr = measureTime([]() {
std::shared_ptr<TestObject> obj = std::make_shared<TestObject>(10);
for (int i = 0; i < N; i++) {
std::shared_ptr<TestObject> tmp(obj);
}
});
// Measure time taken to intrusive_ptr copy ctor
auto timeTakenIntrusivePtr = measureTime([]() {
boost::intrusive_ptr<TestObjectIntrusive> obj(new TestObjectIntrusive(10));
for (int i = 0; i < N; i++) {
boost::intrusive_ptr<TestObjectIntrusive> tmp(obj);
}
});
std::cout << "Time taken for shared_ptr: " << timeTakenSharedPtr << "s" << std::endl;
std::cout << "Time taken for intrusive_ptr: " << timeTakenIntrusivePtr << "s" << std::endl;
return 0;
}
Time taken for shared_ptr: 0.027622s
Time taken for intrusive_ptr: 0.016726s
The code is compiled with the flag -O0
. The result shows that intrusive_ptr
is faster than shared_ptr
. This is because the reference count of intrusive_ptr
is inside the object, which is cache-friendly. In contrast, the memory for the reference count in shared_ptr
must be dynamically allocated and thus is accessed indirectly.
In summary, intrusive_ptr
is a faster alternative for shared_ptr
. But it requires the developer to implement the logics of reference counting inside the custom class. For latency-sensitive applications, intrusive_ptr
is a good choice.
Related reading: