Example to use shared_ptr?
Asked Answered
M

7

84

Hi I asked a question today about How to insert different types of objects in the same vector array and my code in that question was

 gate* G[1000];
G[0] = new ANDgate() ;
G[1] = new ORgate;
//gate is a class inherited by ANDgate and ORgate classes
class gate
{
 .....
 ......
 virtual void Run()
   {   //A virtual function
   }
};
class ANDgate :public gate 
  {.....
   .......
   void Run()
   {
    //AND version of Run
   }  

};
 class ORgate :public gate 
  {.....
   .......
   void Run()
   {
    //OR version of Run
   }  

};      
//Running the simulator using overloading concept
 for(...;...;..)
 {
  G[i]->Run() ;  //will run perfectly the right Run for the right Gate type
 } 

and I wanted to use vectors so someone wrote that I should do that :

std::vector<gate*> G;
G.push_back(new ANDgate); 
G.push_back(new ORgate);
for(unsigned i=0;i<G.size();++i)
{
  G[i]->Run();
}

but then he and many others suggested that I would better use Boost pointer containers
or shared_ptr. I have spent the last 3 hours reading about this topic, but the documentation seems pretty advanced to me . ****Can anyone give me a small code example of shared_ptr usage and why they suggested using shared_ptr. Also are there other types like ptr_vector, ptr_list and ptr_deque** **

Edit1: I have read a code example too that included:

typedef boost::shared_ptr<Foo> FooPtr;
.......
int main()
{
  std::vector<FooPtr>         foo_vector;
........
FooPtr foo_ptr( new Foo( 2 ) );
  foo_vector.push_back( foo_ptr );
...........
}

And I don't understand the syntax!

Marashio answered 13/8, 2010 at 12:48 Comment(1)
Which syntax don't you understand? The first line of main creates a vector that can contain shared pointers to a type called Foo; the second one creates a Foo using new, and a shared pointer to manage it; the third puts a copy of the shared pointer into the vector.Bramwell
N
119

Using a vector of shared_ptr removes the possibility of leaking memory because you forgot to walk the vector and call delete on each element. Let's walk through a slightly modified version of the example line-by-line.

typedef boost::shared_ptr<gate> gate_ptr;

Create an alias for the shared pointer type. This avoids the ugliness in the C++ language that results from typing std::vector<boost::shared_ptr<gate> > and forgetting the space between the closing greater-than signs.

    std::vector<gate_ptr> vec;

Creates an empty vector of boost::shared_ptr<gate> objects.

    gate_ptr ptr(new ANDgate);

Allocate a new ANDgate instance and store it into a shared_ptr. The reason for doing this separately is to prevent a problem that can occur if an operation throws. This isn't possible in this example. The Boost shared_ptr "Best Practices" explain why it is a best practice to allocate into a free-standing object instead of a temporary.

    vec.push_back(ptr);

This creates a new shared pointer in the vector and copies ptr into it. The reference counting in the guts of shared_ptr ensures that the allocated object inside of ptr is safely transferred into the vector.

What is not explained is that the destructor for shared_ptr<gate> ensures that the allocated memory is deleted. This is where the memory leak is avoided. The destructor for std::vector<T> ensures that the destructor for T is called for every element stored in the vector. However, the destructor for a pointer (e.g., gate*) does not delete the memory that you had allocated. That is what you are trying to avoid by using shared_ptr or ptr_vector.

Nardone answered 13/8, 2010 at 13:36 Comment(2)
That was detailed :) . My Question is regarding the 3rd line of code gate_ptr ptr(new ANDgate); It doesn't feel pretty familiar to me , ptr of a type shared pointer gate and then between braces you sent a new ANDgate ! That is confusing .Marashio
@Ahmed: the overall expression is a variable initialisation, just like you might write int x(5); to initialise x with the value 5. In this case, it's being initialised with the value of the new-expression which creates an ANDgate; the new-expression's value is a pointer to the new object.Bramwell
K
43

I will add that one of the important things about shared_ptr's is to only ever construct them with the following syntax:

shared_ptr<Type>(new Type(...));

This way, the "real" pointer to Type is anonymous to your scope, and held only by the shared pointer. Thus it will be impossible for you to accidentally use this "real" pointer. In other words, never do this:

Type* t_ptr = new Type(...);
shared_ptr<Type> t_sptr ptrT(t_ptr);
//t_ptr is still hanging around!  Don't use it!

Although this will work, you now have a Type* pointer (t_ptr) in your function which lives outside the shared pointer. It's dangerous to use t_ptr anywhere, because you never know when the shared pointer which holds it may destruct it, and you'll segfault.

Same goes for pointers returned to you by other classes. If a class you didn't write hands you a pointer, it's generally not safe to just put it in a shared_ptr. Not unless you're sure that the class is no longer using that object. Because if you do put it in a shared_ptr, and it falls out of scope, the object will get freed when the class may still need it.

Kashmiri answered 13/8, 2010 at 17:23 Comment(3)
Everything Ken said is good and true, but I believe the preferred way to call it now is auto t_ptr = make_shared<Type>(...); or equivalently shared_ptr<Type> t_ptr = make_shared<Type>(...);, simply because that form is more efficient.Demulsify
@KenSimon, is there supposed to be a comma , between t_sptr and ptrT in shared_ptr<Type> t_sptr ptrT(t_ptr);?Sibel
Aside from the ambiguities in the example code, good warning - but shame you have to make it, since the 1st form is so much cleaner, and perhaps more importantly, surely anyone using a smart pointer knows that it exists precisely to avoid having dangerous raw pointers floating around. The final paragraph is interesting; thankfully I've not yet worked with any library that forces me to use points of raw or unclear type, though I'm sure it'll happen some time.Wednesday
J
22

Learning to use smart pointers is in my opinion one of the most important steps to become a competent C++ programmer. As you know whenever you new an object at some point you want to delete it.

One issue that arise is that with exceptions it can be very hard to make sure a object is always released just once in all possible execution paths.

This is the reason for RAII: http://en.wikipedia.org/wiki/RAII

Making a helper class with purpose of making sure that an object always deleted once in all execution paths.

Example of a class like this is: std::auto_ptr

But sometimes you like to share objects with other. It should only be deleted when none uses it anymore.

In order to help with that reference counting strategies have been developed but you still need to remember addref and release ref manually. In essence this is the same problem as new/delete.

That's why boost has developed boost::shared_ptr, it's reference counting smart pointer so you can share objects and not leak memory unintentionally.

With the addition of C++ tr1 this is now added to the c++ standard as well but its named std::tr1::shared_ptr<>.

I recommend using the standard shared pointer if possible. ptr_list, ptr_dequeue and so are IIRC specialized containers for pointer types. I ignore them for now.

So we can start from your example:

std::vector<gate*> G; 
G.push_back(new ANDgate);  
G.push_back(new ORgate); 
for(unsigned i=0;i<G.size();++i) 
{ 
  G[i]->Run(); 
} 

The problem here is now that whenever G goes out scope we leak the 2 objects added to G. Let's rewrite it to use std::tr1::shared_ptr

// Remember to include <memory> for shared_ptr
// First do an alias for std::tr1::shared_ptr<gate> so we don't have to 
// type that in every place. Call it gate_ptr. This is what typedef does.
typedef std::tr1::shared_ptr<gate> gate_ptr;    
// gate_ptr is now our "smart" pointer. So let's make a vector out of it.
std::vector<gate_ptr> G; 
// these smart_ptrs can't be implicitly created from gate* we have to be explicit about it
// gate_ptr (new ANDgate), it's a good thing:
G.push_back(gate_ptr (new ANDgate));  
G.push_back(gate_ptr (new ORgate)); 
for(unsigned i=0;i<G.size();++i) 
{ 
   G[i]->Run(); 
} 

When G goes out of scope the memory is automatically reclaimed.

As an exercise which I plagued newcomers in my team with is asking them to write their own smart pointer class. Then after you are done discard the class immedietly and never use it again. Hopefully you acquired crucial knowledge on how a smart pointer works under the hood. There's no magic really.

Jaela answered 13/8, 2010 at 13:12 Comment(3)
My instructor gave me a similar advice about writing my own classes so I will try that for sure . TY .Marashio
you should use an iterator to run all gates for( auto itt = G.begin(); itt != G.end(); ++itt ){ itt->Run(); }Boatsman
Or better yet the new "foreach" in C++Jaela
D
2

The boost documentation provides a pretty good start example: shared_ptr example (it's actually about a vector of smart pointers) or shared_ptr doc The following answer by Johannes Schaub explains the boost smart pointers pretty well: smart pointers explained

The idea behind(in as few words as possible) ptr_vector is that it handles the deallocation of memory behind the stored pointers for you: let's say you have a vector of pointers as in your example. When quitting the application or leaving the scope in which the vector is defined you'll have to clean up after yourself(you've dynamically allocated ANDgate and ORgate) but just clearing the vector won't do it because the vector is storing the pointers and not the actual objects(it won't destroy but what it contains).

 // if you just do
 G.clear() // will clear the vector but you'll be left with 2 memory leaks
 ...
// to properly clean the vector and the objects behind it
for (std::vector<gate*>::iterator it = G.begin(); it != G.end(); it++)
{
  delete (*it);
}

boost::ptr_vector<> will handle the above for you - meaning it will deallocate the memory behind the pointers it stores.

Donnydonnybrook answered 13/8, 2010 at 13:13 Comment(3)
shared_ptr is a smart pointer - a shiny "wrapper" for a plain pointer that let's say adds some AI to a pointer type. ptr_vector is a smart container for pointers - a "wrapper" for a container of pointers.Donnydonnybrook
so ptr_vector is a kind of replacement to normal vector ?Marashio
@Marashio I guess you can think of it like that.Donnydonnybrook
C
2

Through Boost you can do it >

std::vector<boost::any> vecobj;
    boost::shared_ptr<string> sharedString1(new string("abcdxyz!"));    
    boost::shared_ptr<int> sharedint1(new int(10));
    vecobj.push_back(sharedString1);
    vecobj.push_back(sharedint1);

> for inserting different object type in your vector container. while for accessing you have to use any_cast, which works like dynamic_cast, hopes it will work for your need.

Cedric answered 29/5, 2013 at 13:42 Comment(0)
B
1
#include <memory>
#include <iostream>

class SharedMemory {
    public: 
        SharedMemory(int* x):_capture(x){}
        int* get() { return (_capture.get()); }
    protected:
        std::shared_ptr<int> _capture;
};

int main(int , char**){
    SharedMemory *_obj1= new SharedMemory(new int(10));
    SharedMemory *_obj2 = new SharedMemory(*_obj1);
    std::cout << " _obj1: " << *_obj1->get() << " _obj2: " << *_obj2->get()
    << std::endl;
    delete _obj2;

    std::cout << " _obj1: " << *_obj1->get() << std::endl;
    delete _obj1;
    std::cout << " done " << std::endl;
}

This is an example of shared_ptr in action. _obj2 was deleted but pointer is still valid. output is, ./test _obj1: 10 _obj2: 10 _obj2: 10 done

Blaspheme answered 10/12, 2013 at 14:29 Comment(0)
A
0

The best way to add different objects into same container is to use make_shared, vector, and range based loop and you will have a nice, clean and "readable" code!

typedef std::shared_ptr<gate> Ptr   
vector<Ptr> myConatiner; 
auto andGate = std::make_shared<ANDgate>();
myConatiner.push_back(andGate );
auto orGate= std::make_shared<ORgate>();
myConatiner.push_back(orGate);

for (auto& element : myConatiner)
    element->run();
Anticipant answered 31/3, 2018 at 16:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.