Smart Pointers in Container Like Std::Vector

Smart pointers in container like std::vector?

Yes, you really can't use std::auto_ptr with standard containers. std::auto_ptr copies aren't equivalent, and because standard containers (and algorithms) are allowed to copy their elements at will this screws things up. That is, the operation of copying a std::auto_ptr has a meaning other than a mere copy of an object: it means transferring an ownership.

Your options are:

  1. Use the Boost Smart Pointers library. This is arguably your best option.
  2. Use primitive pointers. This is fast and safe, so long as you manage the pointers properly. At times this can be complex or difficult. For example, you'll have to cope with (avoid) double-delete issues on your own.
  3. Use your own reference-counting smart pointer. That'd be silly; use a Boost Smart Pointer.

C++ Smart Pointers in a Vector container

Your code doesn't contain a memory leak. So your first point is fine.

No, if you don't call shapes.clear(), there will not be a memory leak since std::vector's destructor cleans up the container.

I don't know a specific rule about using shared pointers in a container so I'll skip on your third question.

However, I can offer an improvement for creating std::shared_ptrs:

When you create a shared pointer using its constructor, ie shared_ptr<T>(new T());, the control block or the structure that holds bookkeeping information about that resource, is created separately from the object it points to. This might cause cache misses a lot. However, if you create a shared_ptr by using std::make_shared, the control block is allocated with the object you want, therefore, by keeping them together, you can at least mitigate the cost of cache misses: std::make_shared<T>();. For example: std::make_shared<Circle>(3) is equivalent to writing std::shared_ptr<Shape>(new Circle(3)), but usually better.

Containers vs Smart pointers in C++

I would use the vector. Your pointer version offers basically no improvements over the vector, and you lose a lot of useful functionality. You're most likely going to need to measure the size and iterate your array at some point, with a vector you get this for free, whereas you'd need to implement it yourself for your pointer version; at which point you may as well have just used the vector to begin with.

There may be a performance cost instantiating the vector, but I doubt that it would be a bottleneck for most applications. If you're creating so many vectors that instantiating them is costing you time, you can probably be smarter about managing them (pooling your memory, custom vector allocators, etc). If in doubt, measure.

One example where you might need to use the unique_ptr<> version might be if you're working with a library written in C where you lose ownership of the array. For example:

std::unique_ptr<unsigned char[]>myArray(
new unsigned char[3 * outputImageHight * outputImageWidth]);

my_c_lib_data_t cLibData;
int result = my_c_lib_set_image(cLibData, myArray);

if (MYLIB_SUCCESS == result)
// mylib successfully took ownership of the char array, so release the pointer.
myArray.release();

If you have the choice though, prefer to use C++ style containers where you can.

C++ Use of smart pointers inside STL containers

If the objects are pointers it is not enough to manage the memory the pointers occupy. You also need to manage what the pointers point to. It is a good idea to store the objects pointed to instead of the pointers (in case of your example std::vector<int> would be appropriate), however, in case you have polymorphic objects that is not possible.

Performance of smart pointer and raw pointer in containers

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.

Raw pointer, smart pointer or std::vector for low-level container data in C++

I would use std::vector because it solves memory allocation, deallocation, indexing, copying, etc.. Unless you will be using "millions" of matrices at the same time, the extra member (capacity) is probably not relevant.

In any case, optimizing the library for speed is the last thing you want to do -- after you can test the actual speed of your initial implementation. Then you can decide if it is worth spending time to effectively duplicate std::vector functionality with your own implementation.

C++: how does C++ know to destruct smart pointers inside containers?

Whether the unique_ptr is on the stack or the heap doesn't really matter. What matters is that it will automatically call delete on the target pointer when its destructor runs.

When the vector goes out of scope, the vector's destructor runs. That destroys all contained elements (calling their destructors in the process) and then deallocates its heap buffer.

Handling smart pointers in stl container

Q1: What to do with Foo::at( int ) const such that you can:

myfoo.at(i)->doSomething(param1, param2, ...);

without transferring ownership out of the vector<unique_ptr<Shape<T>>>.

A1: Foo::at( int ) const should return a const std::unique_ptr<Shape<T> >&:

template < typename T >
const std::unique_ptr<Shape<T> >&
Foo<T>::at( int index ) const
{
return m_Bank[index];
}

Now your can dereference the const unique_ptr and call any member of Shape they want (const or non-const). If they accidentally try to copy the unique_ptr, (which would transfer ownership out of Foo) they will get a compile time error.

This solution is better than returning a non-const reference to unique_ptr as it catches accidental ownership transfers out of Foo. However if you want to allow ownership transfers out of Foo via at, then a non-const reference would be more appropriate.

Q2: Furthermore, I found recently on the web an example on how to overload the assignment operator using std::move. I usually follow the Copy-Swap idiom. Which of those two ways for overloading the mentioned operator makes sense for my case?

A2: I'm not sure what ~Foo() does. If it doesn't do anything, you could remove it, and then (assuming fully conforming C++11) you would automatically get correct and optimal move constructor and move assignment operator (and the proper deleted copy semantics).

If you can't remove ~Foo() (because it does something important), or if your compiler does not yet implement automatic move generation, you can supply them explicitly, as you have done in your question.

Your move constructor is spot on: Move construct the member.

Your move assignment should be similar (and is what would be automatically generated if ~Foo() is implicit): Move assign the member:

template < typename T >
Foo<T> & Foo<T>::operator =( Foo<T> && bank )
{
m_Bank = std::move(bank.m_Bank);
return (*this);
}

Your Foo design lends itself to being Swappable too, and that is always good to supply:

friend void swap(Foo& x, Foo& y) {x.m_Bank.swap(y.m_Bank);}

Without this explicit swap, your Foo is still Swappable using Foo's move constructor and move assignment. However this explicit swap is roughly twice as fast as the implicit one.

The above advice is all aimed at getting the very highest performance out of Foo. You can use the Copy-Swap idiom in your move assignment if you want. It will be correct and slightly slower. Though if you do be careful that you don't get infinite recursion with swap calling move assignment and move assignment calling swap! :-) Indeed, that gotcha is just another reason to cleanly (and optimally) separate swap and move assignment.

Update

Assuming Shape looks like this, here is one way to code the move constructor, move assignment, copy constructor and copy assignment operators for Foo, assuming Foo has a single data member:

std::vector< std::unique_ptr< Shape > > m_Bank;

...

Foo::Foo(Foo&& other)
: m_Bank(std::move(other.m_Bank))
{
}

Foo::Foo(const Foo& other)
{
for (const auto& p: other.m_Bank)
m_Bank.push_back(std::unique_ptr< Shape >(p ? p->clone() : nullptr));
}

Foo&
Foo::operator=(Foo&& other)
{
m_Bank = std::move(other.m_Bank);
return (*this);
}

Foo&
Foo::operator=(const Foo& other)
{
if (this != &other)
{
m_Bank.clear();
for (const auto& p: other.m_Bank)
m_Bank.push_back(std::unique_ptr< Shape >(p ? p->clone() : nullptr));
}
return (*this);
}

If your compiler supports defaulted move members, the same thing could be achieved with:

    Foo(Foo&&) = default;
Foo& operator=(Foo&&) = default;

for the move constructor and move assignment operator.

The above ensures that at all times each Shape is owned by only one smart pointer/vector/Foo. If you would rather that multiple Foos share ownership of Shapes, then you can have as your data member:

std::vector< std::shared_ptr< Shape > > m_Bank;

And you can default all of move constructor, move assignment, copy constructor and copy assignment.

Container class with vector of smart pointers to abstract base class

1. Declare virtual destructor

class AbstractBase
{
public:
AbstractBase() { }
virtual ~AbstractBase() = default; // this is (defaulted) virtual destructor
virtual std::string toString() = 0;
};

class Derived : public AbstractBase
{
public:
Derived() { }
virtual std::string toString() override { return "Just an example " + std::to_string( _value ); }
private:
int _value;
};

2. Store shared_ptr's in vector

std::vector<std::shared_ptr<AbstractBase>> v = { std::make_shared<Derived>() };

3. Do your stuff and not care about destructors any more.

for (auto i : v)
{
std::cout << i->toString() << std::endl;
}


Related Topics



Leave a reply



Submit