神刀安全网

Top dumb mistakes to avoid with C++ 11 smart pointers

I love the new C++ 11 smart pointers. In many ways, they were a godsent for many folks who hate managing their own memory. In my opinion, it made teaching C++ to newcomers much easier.

However, in the two plus years that I’ve been using them extensively, I’ve come across multiple cases where improper use of the C++ 11 smart pointers made the program inefficient or simply crash and burn. I’ve catalogued them below for easy reference. 

Before we begin, let’s take a look at a simple Aircraft class we’ll use to illustrate the mistakes.

class Aircraft { private:  string m_model;   public:    int m_flyCount;    weak_ptr<aircraft> myWingMan;    void Fly()  {   cout << "Aircraft type" << m_model << "is flying !" << endl;  }    Aircraft(string model)  {   m_model = model;   cout << "Aircraft type " << model << " is created" << endl;  }    Aircraft()  {   m_model = "Generic Model";   cout << "Generic Model Aircraft created." << endl;  }    ~Aircraft()  {   cout << "Aircraft type  " << m_model << " is destroyed" << endl;  }   }; </aircraft>

Mistake # 1 : Using a shared pointer where an unique pointer suffices !!!

I’ve recently been working in an inherited codebase which uses a shared_ptr for creating and managing every object. When I analyzed the code, I found that in 90% of the cases, the resource wrapped by the shared_ptr is not shared.

This is problematic because of two reasons:

1. If you have a resource that’s really meant to be owned exclusively, using a shared_ptr  instead of a unique_ptr makes the code susceptible to unwanted resource leaks and bugs.

  • Subtle Bugs: Just imagine if you never imagined a scenario where the resource is shared out by some other programmer by  assigning it to another shared pointer which inadvertently modifies the resource !
  • Unnecessary Resource Utilization: Even if the other pointer does not modify the shared resource, it might hang on to it far longer than necessary thereby hogging your RAM unnecessarily even after the original shared_ptr goes out of scope.

2. Creating a shared_ptr is more resource intensive than creating a unique_ptr.

  • A shared_ptr needs to maintain the threadsafe refcount of objects it points to and a control block under the covers which makes it more heavyweight than an unique_ptr.

Recommendation –By default, you should use a unique_ptr. If a requirement comes up later to share the resource ownership, you can always change it to a shared_ptr.

Mistake # 2 : Not making resources/objects shared by shared_ptr threadsafe !

Shared_ptr allows you to share the resource thorough multiple pointers which can essentially be used from multiple threads. It’s a common mistake to assume that wrapping an object up in a shared_ptr makes it inherently thread safe. It’s still your responsibility to put synchronization primitives around the shared resource managed by a shared_ptr.

Recommendation – If you do not plan on sharing the resource between multiple threads, use a unique_ptr.

Mistake # 3 : Using auto_ptr !

The auto_ptr feature was outright dangerous and has now been deprecated. The transfer of ownership executed by the copy constructor when the pointer is passed by value can cause fatal crashes in the system when the original auto pointer gets dereferenced again. Consider an example:

int main() {  auto_ptr<aircraft> myAutoPtr(new Aircraft("F-15"));  SetFlightCountWithAutoPtr(myAutoPtr); // Invokes the copy constructor for the auto_ptr  myAutoPtr->m_flyCount = 10; // CRASH !!! } </aircraft>

Recommendation– unique_ptr does what auto_ptr was intended to do. You should do a search and find on your codebase and replace all auto_ptr with unique_ptr. This is pretty safe but don’t forget to retest your code ! ��

Mistake # 4 : Not using make_shared to initialize a shared_ptr !

make_shared has two distinct advantages over using a raw pointer:

1. Performance : When you create an object with new , and then create a shared_ptr , there are two dynamic memory allocations that happen : one for the object itself from the new, and then a second for the manager object created by the shared_ptr constructor.

shared_ptr<aircraft> pAircraft(new Aircraft("F-16")); // Two Dynamic Memory allocations - SLOW !!! </aircraft>

On the contrary, when you use make_shared, C++ compiler does a single memory allocation big enough to hold both the manager object and the new object.

shared_ptr<aircraft> pAircraft = make_shared<aircraft>("F-16"); // Single allocation - FAST ! </aircraft></aircraft>

2. Safety: Consider the situation where the Aircraft object is created and then for some reason the shared pointer fails to be created. In this case, the Aircraft object will not be deleted and will cause memory leak !

Recommendation : Use make_shared to instantiate shared pointers instead of using the raw pointer.

Mistake # 5 : Not assigning an object(raw pointer) to a shared_ptr as soon as it is created !

An object should be assigned to a shared_ptr as soon as it is created. The raw pointer should never be used again.

Consider the following example:

int main() {  Aircraft* myAircraft = new Aircraft("F-16");    shared_ptr<aircraft> pAircraft(myAircraft);  cout << pAircraft.use_count() << endl; // ref-count is 1    shared_ptr<aircraft> pAircraft2(myAircraft);  cout << pAircraft2.use_count() << endl; // ref-count is 1    return 0; } </aircraft></aircraft>

It’ll cause an ACCESS VIOLATION and crash the program !!!

The problem is that when the first shared_ptr goes out of scope, the myAircraft object is destroyed. When the second shared_ptr goes out of scope , it tries to destroy the previously destroyed object again !

Recommendation: If you’re not using make_shared to create the shared_ptr , at least create the object managed by the smart pointer in the same line of code – like :

shared_ptr<aircraft> pAircraft(new Aircraft("F-16")); </aircraft>

Mistake # 6 : Deleting the raw pointer used by the shared_ptr !

You can get a handle to the raw pointer from a shared_ptr using the shared_ptr.get() api. However, this is risky and should be avoided. Consider the following piece of code:

void StartJob() {  shared_ptr<aircraft> pAircraft(new Aircraft("F-16"));  Aircraft* myAircraft = pAircraft.get(); // returns the raw pointer  delete myAircraft;  // myAircraft is gone } </aircraft>

Once we get the raw pointer (myAircraft) from the shared pointer, we delete it. However, once the function ends, the shared_ptr pAircraft goes out of scope and tries to delete the myAircraft object which has already been deleted. The result is an all too familiar ACCESS VIOLATION !

Recommendation : Think really hard before you pull out the raw pointer from the shared pointer and hang on to it. You never know when someone is going to call delete on the raw pointer and cause your shared_ptr to Access Violate.

Mistake # 7 : Not using a custom deleter when using an array of pointers with a shared_ptr !

Consider the following piece of code:

void StartJob() {  shared_ptr<aircraft> ppAircraft(new Aircraft[3]); } </aircraft>

The shared pointer will just point to Aircraft[0] — Aircraft[1] and Aircraft[2] have memory leaks will not be cleaned up when the smart pointer goes out of scope. If you’re using Visual Studio 2015, you’ll get a heap corruption error.

Recommendation: Always pass a custom delete with array objects managed by shared_ptr. The following code fixes the issue:

void StartJob() {  shared_ptr<aircraft> ppAircraft(new Aircraft[3], [](Aircraft* p) {delete[] p; }); } </aircraft>

Mistake # 8 : Not avoiding cyclic references when using shared pointers !

In many situations , when a class contains a shared_ptr reference , you can get into cyclical references. Consider the following scenario – we want to create two Aircraft objects – one flown my Maverick and one flown by Iceman ( I could not help myself from using the TopGun reference !!! ). Both maverick and Iceman needs to hold a reference to each Other Wingman.

So our initial design introduced a self referencial shared_ptr inside the Aircraft class:

class Aircraft
{
private:
string m_model;
public:
int m_flyCount;
shared_ptr<Aircraft> myWingMan;
….

Then in our  main() , we create Aircraft objects, Maverick and Goose , and make them each other’s wingman:

int main() {  shared_ptr<aircraft> pMaverick = make_shared<aircraft>("Maverick: F-14");  shared_ptr<aircraft> pIceman = make_shared<aircraft>("Iceman: F-14");    pMaverick->myWingMan = pIceman; // So far so good - no cycles yet  pIceman->myWingMan = pMaverick; // now we got a cycle - neither maverick nor goose will ever be destroyed    return 0; } </aircraft></aircraft></aircraft></aircraft>

When main() returns, we expect the two shared pointers to be destroyed – but neither is because they contain cyclical references to one another. Even though the smart pointers themselves gets cleaned from the stack, the objects holding each other references keeps both the objects alive.

Here’s the output of running the program:

Aircraft type Maverick: F-14 is created

Aircraft type Iceman: F-14 is created

So what’s the fix ? we can change the shared_ptr inside the Aircraft class to a weak_ptr !Here’s the output after re-executing the main().

Aircraft type Maverick: F-14 is created

Aircraft type Iceman: F-14 is created

Aircraft type  Iceman: F-14 is destroyed

Aircraft type  Maverick: F-14 is destroyed

Notice how both the Aircraft objects were destroyed.

Recommendation: Consider using weak_ptr in your class design when ownership of the resource is not needed and you don’t want to dictate the lifetime of the object.

Mistake # 9 : Not deleting a raw pointer returned by unique_ptr.release() !

The Release() method does not destroy the object managed by the unique_ptr, but the unique_ptr object is released from the responsibility of deleting the object. Someone else (YOU!) must delete this object manually.

The following code below causes a memory leak because the Aircraft object is still alive at large once the main() exits.

int main() {  unique_ptr<aircraft> myAircraft = make_unique<aircraft>("F-22");  Aircraft* rawPtr = myAircraft.release();  return 0; } </aircraft></aircraft>

Recommendation:Anytime you call Release() on an unique_ptr, remember to delete the raw pointer. If your intent is to delete the object managed by the unique_ptr, consider using unique_ptr.reset().

Mistake # 10 : Not using a expiry check when calling weak_ptr.lock() !

Before you can use a weak_ptr, you need to acquire the weak_ptr by calling a lock() method on the weak_ptr. The lock() method essentially upgrades the weak_ptr to a shared_ptr such that you can use it. However, if the shared_ptr object that the weak_ptr points to is no longer valid, the weak_ptr is emptied. Calling any method on an expired weak_ptr will cause an ACESS VIOLATION.

For example, in the code snippet below, the shared_ptr that “mywingMan” weak_ptr is pointing to has been destroyed via pIceman.reset(). If we execute any action now via myWingman weak_ptr, it’ll cause an access violation.

int main() {  shared_ptr<aircraft> pMaverick = make_shared<aircraft>("F-22");  shared_ptr<aircraft> pIceman = make_shared<aircraft>("F-14");    pMaverick->myWingMan = pIceman;  pIceman->m_flyCount = 17;    pIceman.reset(); // destroy the object managed by pIceman    cout << pMaverick->myWingMan.lock()->m_flyCount << endl; // ACCESS VIOLATION    return 0; } </aircraft></aircraft></aircraft></aircraft>

It can be fixed easily by incorporating the following if check before using the myWingMan weak_ptr.

if (!pMaverick->myWingMan.expired())  {   cout << pMaverick->myWingMan.lock()->m_flyCount << endl;  }

Recommendation:Always check if a weak_ptr is valid via the expired() function before using it in your code.

So, What’s Next ?

If you want to learn more about the nuances of C++ 11 smart pointers or C++ 11 in general, I recommend the following books.

1. C++ Primer (5th Edition) by Stanley Lippman

2. Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14 by Scott Meyers

All the best in your journey of exploring C++ 11 further. Please share if you liked the article. ��

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Top dumb mistakes to avoid with C++ 11 smart pointers

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址