How Much Is the Overhead of Smart Pointers Compared to Normal Pointers in C++

How much is the overhead of smart pointers compared to normal pointers in C++?

std::unique_ptr has memory overhead only if you provide it with some non-trivial deleter.

std::shared_ptr always has memory overhead for reference counter, though it is very small.

std::unique_ptr has time overhead only during constructor (if it has to copy the provided deleter and/or null-initialize the pointer) and during destructor (to destroy the owned object).

std::shared_ptr has time overhead in constructor (to create the reference counter), in destructor (to decrement the reference counter and possibly destroy the object) and in assignment operator (to increment the reference counter). Due to thread-safety guarantees of std::shared_ptr, these increments/decrements are atomic, thus adding some more overhead.

Note that none of them has time overhead in dereferencing (in getting the reference to owned object), while this operation seems to be the most common for pointers.

To sum up, there is some overhead, but it shouldn't make the code slow unless you continuously create and destroy smart pointers.

C++ smart pointer performance and difference with a simple wrapped pointer

As best I can tell, the actual question is:

I was wondering, does the unique_pointer do anything special that I would lose if I used my own smart pointer? It seems to be rather heavy, well at least in debug mode it seems to be doing a lot.

It is possible that unique_ptr may have more trivial function calls or something like that, which doesn't get fully inlined, leading to worse performance in debug mode. However, as you said yourself, the performance when it matters, with optimizations enabled, is the same.

Even though unique_ptr is the simplest owning smart pointer to write, it still does a lot of things that your trivial wrapper does not:

  • It allows custom deleters, while ensuring that stateless custom deleters don't use extra space through Empty Base Class Optimization
  • It handles moves and copies correctly
  • It handles all kinds of conversions correctly; for instance unique_ptr<Derived> will implicitly convert to unique_ptr<Base>
  • it's const correct

Although most decent C++ programmers can implement a decent unique_ptr, I don't think most can implement one that is fully correct. And those edge cases will hurt you.

Just use unique_ptr, rolling your own for better performance with optimizations off is not a good reason.

How efficient smart pointers are?

The rule of thumb is:

  1. If you can, use a stack based object directly or by a reference instead of using pointers.
  2. Else, if you don't have to deal with shared ownership (usually you don't) use unique_ptr - it is not only faster, but also safer (no circular references).
  3. Else, if you do have shared ownership, use shared_ptr

Raw pointers are OK in certain circumstances when they don't carry ownership - for instance as an input argument to a function:

void draw (const shape* sh) {
sh->draw();
}

...
std::unique_ptr<shape> ptr(new triangle);
draw(ptr.get());

How much memory do 1000 shared pointers take?

In a typical implementation, std::shared_ptr holds only two pointers.

So 1000 shared pointers take up 1000 * 2 * sizeof(pointer) bytes of memory.

  • Size of a pointer is 4 bytes on all 32-bit systems that follow ILP32 data model.
  • Size of a pointer is 8 bytes on a 64-bit systems that follow LP64 data model (Most Unix and Unix-like systems) or LLP64 data model (Microsoft Windows x86-64).

Note: The size of the control block (which is implementation dependent) and the size of the object the shared pointer shares ownership of are not part of this.

std::unique_ptr memory and performance

There likely is no "raw ptr in smart ptr". The smart pointer will just be the raw pointer. There is nothing you need that indirection would give you.

C++ smart pointers dereferencing - what's the overhead of checking whether it is initialized?

Compile your program with -DBOOST_DISABLE_ASSERTS and make the comparison. But I will venture saying that the performance penalty for that assertion is negligible. As @Travis said, probably the assertions are not even going into your program in Release mode.

How to use smart pointer from function that returns a raw pointer

It depends. Consider this example:

struct example {
~example() { std::cout << "bye\n"; }
};

example* some_lib_function_A(){
return new example;
}

The library returns you a raw pointer to a dynamically allocated object. That's not nice, and to avoid dealing with the raw owning pointer you can wrap some_lib_function_A into a function that returns a smart pointer that manages the object.

However, the library might also do something along the line of this (just for the sake of the argument. It should rather return a reference):

example* some_lib_function_B() {
static example ex;
return &ex;
}

And in this case you cannot delete the returned pointer without running into problems.

Complete example:

#include <memory>
#include <iostream>
struct example {
~example() { std::cout << "bye\n"; }
};

example* some_lib_function_A(){
return new example;
}

example* some_lib_function_B() {
static example ex;
return &ex;
}

template <typename F>
std::unique_ptr<example> wrap(F f){
std::unique_ptr<example> res;
res.reset(f());
return res;
}

int main() {
wrap(some_lib_function_A);
wrap(some_lib_function_B); // free(): invalid pointer
}

The first line in main does what you would expect, but the second line causes undefined behavior. Thats the reason the linked answer says that it is bad to write a function along the line of:

 std::unique_ptr<example> wrap_bad(example& ex) {
std::unique_ptr<example> res;
res.reset(&ex);
return res;
}

Because you cannot know if the example passed to the function was dynamically allocated or not. The function is lying about what it does, because a function taking a reference has no business in dealing with lifetime of its parameter. It can be used correctly, but it has great potential to be used wrong.

You have to read the libraries documentation and find out what the library expects you to do with the pointer. Sometimes you must call some library function clean_up(ex) for proper clean up, in which case you can wrap the library clean up function in a custom deleter.



Related Topics



Leave a reply



Submit