Search code examples
c++c++20

Performance of smart pointer and raw pointer in containers


I'm curious about the answer to this question as I mostly work with containers. which one is more logical to use in minimum of 100 (and maximum of 10k) elements in vector or map container in?

  • std:::vector<std::unique_ptr<(struct or class name)>>
  • std:::vector<std::shared_ptr<(struct or class name)>>
  • std:::vector<(struct or class name)*>

Machine detais: FreeBSD 12.1 + clang-devel or gcc11.


Solution

  • This is really opinion-based, but I'll describe the rules of thumb I use.

    std:::vector<(struct or class name)> is my default unless I have specific requirements that are not met by that option. More specifically, it is my go-to option UNLESS at least one of the following conditions are true;

    • struct or class name is polymorphic and instances of classes derived from struct or class name need to be stored in the vector.
    • struct or class name does not comply with the rule of three (before C++11), the rule of five (from C++11), OR the rule of zero
    • there are SPECIFIC requirements to dynamically manage lifetime of instances of struct or class name

    The above criteria amount to "use std::vector<(struct or class name)> if struct or class name meets requirements to be an element of a standard container".

    If struct or class name is polymorphic AND there is a requirement that the vector contain instances of derived classes my default choice is std:::vector<std::unique_ptr<(struct or class name)> >. i.e. none of the options mentioned in the question.

    I will only go past that choice if there are special requirements for managing lifetime of the objects in the vector that aren't met by either std:::vector<(struct or class name)> or std:::vector<std::unique_ptr<(struct or class name)> >.

    Practically, the above meets the vast majority of real-world needs.

    If there is a need for two unrelated pieces of code to have control over the lifetime of objects stored in a vector then (and only then) I will consider std:::vector<std::shared_ptr<(struct or class name)> >. The premise is that there will be some code that doesn't have access to our vector, but has access to its elements via (for example) being passed a std::shared_ptr<(struct or class name)>.

    Now, I get to the case which is VERY rare in my experience - where there are requirements to manage lifetime of objects that aren't properly handled by std:::vector<(struct or class name)>, std:::vector<std::unique_ptr<(struct or class name)> >, or by std:::vector<std::shared_ptr<(struct or class name)> >.

    In that case, and only that case, I will - and only if I'm desperate - use std:::vector<(struct or class name)*>. This is the situation to be avoided, as much as possible. To give you an idea of how bad I think this option is, I've been known to change other system-level requirements in a quest to avoid this option. The reason I avoid this option like the plague is that it becomes necessary to write and debug EVERY bit of code that explicitly manages the lifetime of each struct or class name. This includes writing new expressions everywhere, ensuring every new expression is eventually matched by a corresponding delete expression. This option also means there is a need to debug hand-written code to ensure no object is deleted twice (undefined behaviour) and every object is deleted once (i.e. avoid leaks). In other words, this option involves lots of effort and - in non-trivial situations - is really hard to get working correctly.