Basics of Container of pointers: Part I
STL containers are value semantic, this imply that when you append an element to it makes a copy of it (vector). Once we have appended an element, modifying the element – such as giving a new value for example, won’t affect the vector element. An element of a container can’t be an element of another (elements of two containers are necessarily distinct objects, even if the elements happen to compare equal), and removing an element from a container destroys that element (although other objects with the same value may exist elsewhere). Finally, a container “owns” its element: when a container is destroyed, all of the elements in it are destroyed along with it.
Now let’s consider a container of pointers (store the addresses’ object). What’s the benefit of using such container and why would you need to have a vector of pointers? One reason it’s because copy can be very expensive (huge container), copy constructor and the copy assignment operator each make a deep copy of the host object; i.e. exact copies of the data addressed by the host object’s pointers. Unfortunately, this approach can be prohibitively expensive in terms of system resources and is generally avoided. Polymorphism is a very good reason for using such container (holding pointers allow effective use of polymorphism (i.e., virtual functions)). Derived classes in arrays or vectors of base classes are one of the most useful properties of polymorphism. However, the object can’t just be of the type, rather it needs to be a pointer of the base class. The concept of polymorphism is that a pointer of a base class can point to any class that derives from it. If you use standard container of object, you have the so called slicing problem. This happens when an object of a derived class is copied to a base class variable. During the copying process the derived part of the object is “sliced away” and the object is turned into a base class object. This is how the overridden methods get lost (polymorphic property is vanish).To avoid this situation you need to use pointers instead. Pointers can never be sliced.
Pointers are good type to be stored in a container as any another types. Pointers
• take up memory;
• they can be assigned to;
• they have addresses;
• They have values that that can copied;
In one sense, nothing has changed. You still creating a std::vector<T>; it’s just that in this case T happens to be a variable of pointer type, my_type*. The vector still “owns” its element, and they’re still destroyed when the vector itself is. The trouble with using vector<T*> is that, whenever the vector goes out of scope unexpectedly (like when an exception is thrown), the vector cleans up after yourself, but this will only free the memory it manages for holding the pointer, not the memory you allocated for what the pointers are referring to (they’re the pointers, not the things those pointers are pointing to).
Suppose we write:
my_type* ptr=new my_type;
The pointer ptr will disappear when it goes out of scope, but the object it points to, *ptr, will not. If you want to destroy that object and free its memory, you need to do it yourself, either by explicitly writing delete ptr or by some equivalent method. Also, if an exception occurs in-between the allocation of elements and the de-allocation loop, the de-allocation loop would never run and you’re stuck with the memory leak anyway! This is called exception safety and it’s a critical reason why de-allocation needs to be done automatically.
Using pointers gives rise to another important issue, which is magnified when working with containers of pointers. If you have class with pointer members and let the compiler supply the copy constructor and the copy assignment operator, both of which simply duplicate the host object’s pointers. With that approach, if a container of pointers (or an object with a pointer member) were passed as an argument for a value parameter in a function call, the function would have a local copy of that object that would be destroyed when it returned. Assuming that the destructor properly deleted the pointers, the original object would then contain one or more pointers to deleted memory, leading to the kind of memory corruption that is notoriously difficult to trace. Almost guaranteed that your program will crash or lead to memory leak.
What we mean by ownership?
You’d need to think about who “owns” those pointers, so that the owner (and only the owner) would delete them. What is the meaning of container ownership? It’s worth to take few lines to explain this concept, because it’s at the heart of pointer container that we discuss below. “Ownership” means that pointer container take the ownership of the dynamically created objects and the container will destroy or delete all pointed-to objects when it goes out-of-scope or destroyed (container end lifecycle). Below a code snippet that illustrate this concept
void goOutOfScope(){
// check if the dtor is called by pointer container boost::ptr_vector<SomeStruct> w_ptv; w_ptv.reserve(5); // reserve memory // SomeStruct* w_tobeAdded = NULL; // means we allocate space for 5 elements for( boost::ptr_vector<SomeStruct>::size_type i = 0; i < w_ptv.capacity();++i) { // proper way to do it (or recommended way to create it) w_ptv.push_back( new SomeStruct); // return the pointed-to-object, can modify object w_ptv[i].setName( std::string(“Ptr Struct_”)+std::to_string(i)); w_ptv[i].setIID( static_cast<unsigned>(i)); w_ptv[i].setValue(0.4f); } // if (something.isWrong()) // { // throw std::exception(“Something real wrong!”); // } // return; } // pointed-to objects in w_ptv are deleted here! |
boost::ptr_vector (container of pointer)
Because we need some sort management of resource (automatic to prevent leak in our program), Boost container of pointer (“ptr_vector”) provide such of RAII (Resource Acquisition Is Initialization) with the pointer container. “ptr_vector” will free all the elements in its destructor, so you don’t have to do anything more than what you already do, that is, create the objects, add them to the “ptr_vector”, and leave the management to it.
The boost vector has a couple of advantages, implying behaviour similar of the standard C++ vector, except they hold pointer data type, owning their objects through pointers rather than by making copies. You can treat an object like a value, but still use run-time polymorphism via virtual function calls. Header file to include (Boost Pointer Container library) #include <boost/ptr_container/ptr_vector.hpp>
The key features of this library are
- “ptr_vector” is implemented as a wrapper for the standard C++ (STL) vector that cuts one level of indirection for iterators and member functions;
- Syntax of accessing elements is simpler since there isn’t an extra layer of indirection;
- Container owns the objects they have pointers to, i.e., the elements within the container, and will destroy them when they are themselves destroyed. You don’t have to explicitly delete the objects whose pointers you are storing which is error prone and potentially exception ‘dangerous’ at other points in the program;
- Iterators iterate over pointed-to objects, not pointers;
- Some (i.e. the ‘right’) ptr-vector member functions (operator[], front(), back(), …) are provided via references rather than pointers so it makes it more natural to use with the standard algorithms (not such a big deal in C++11 (with lambdas) but it makes C++03 much easier to use);
Compare to Standards vectors
One piece of code is worth a thousand words. The following example compares:
- boost::ptr_vector<T> (container for pointers to objects of type T);
- std::vector<T> (a standard C++ container for objects of type T);
- std::vector<T*> (a Standard C++ container for pointers to objects of type T);
The three code snippets below print the same result:
Hello World! Hello World! Hello World!
//// ptr_vevtor<T>
// boost::ptr_vector<std::string> w_boostvecStr; w_boostvecStr.push_back(new std::string(“Hello, “)); w_boostvecStr.push_back(new std::string(“World! “)); std::cout << w_boostvecStr[0] << w_boostvecStr.at(1) << *w_boostvecStr.begin() << w_boostvecStr.begin()[1] << w_boostvecStr.front() << w_boostvecStr.back() << “\n”; // // vector<T> // std::vector<std::string> w_stdvecStr; w_stdvecStr.push_back(std::string(“Hello, “)); w_stdvecStr.push_back(std::string(“World! “)); std::cout << w_stdvecStr[0] << w_stdvecStr.at(1) << *w_stdvecStr.begin() << w_stdvecStr.begin()[1] << w_stdvecStr.front() << w_stdvecStr.back() << “\n”; // // vector<T*> // std::vector<std::string*> w_stdvecPtr; w_stdvecPtr.push_back(new std::string(“Hello, “)); w_stdvecPtr.push_back(new std::string(“World! “)); std::cout << *w_stdvecPtr[0] << *w_stdvecPtr.at(1) << **w_stdvecPtr.begin() << *w_stdvecPtr.begin()[1] << *w_stdvecPtr.front() << *w_stdvecPtr.back() << “\n”; // explicitly deleting container objects by // calling delete on each element of the vector std::for_each( w_stdvecPtr.begin(),w_stdvecPtr.end(),[]( std::string*& aStrptr) { delete aStrptr; aStrptr=nullptr; }); // empty container if( !w_stdvecPtr.empty()) { // shall contains null pointer, then we need to clear the vector w_stdvecPtr.clear(); assert(w_stdvecPtr.size()==0); } |
At first sight, there is no difference between boost::ptr_vector and std::vector, they both use the same syntax (transparent), this is the beauty of boost::ptr_vector, let you treat pointer container exactly the same way as a container of objects. The std::vector<T*> has many ‘*’ that make the code harder to read with the extra level of indirection. Moreover, it’s easy to forget to add those extra ‘*’ when coding. Boost ptr_vector<T> make the code cleaner and easier to understand. In my next blog I will present some usage of the pointer vector taken from our programming environment (scientific programming framework) in the domain application of Open Channel Flow Simulation (computational physics).
Leave a Reply
Want to join the discussion?Feel free to contribute!