5

I'm seeing a lot of people say you should never use raw pointers. What about in the case where you store the raw pointer in a class but how they get populated is from getting the pointer of an object that wasn't dynamically created? I'm curious about if this is bad practice and if so how to make it better. This seems ideal to me as I'm not required to delete anything myself so limited/no chance for a memory leak? The idea of "if you didn't create it you don't delete it".

I can control everything by the scope of the variable I'm passing in right? If it goes out of scope then what it points to won't be valid anymore and it'll throw an exception if I try to access it and I'll be able to catch it and handle it. Curious on thoughts/comments/concerns on this.

class Object
{
private:
   Object* object;
public:
   void Object() { object = NULL; }
   void AddObject(Object* obj)
   {
      object = obj;
   }

  Object* GetObject()
  {
     return object;
  }
};


int main()
{
   Object obj1;
   Object obj2;

   // obj2 wasn't dynamically created so no need to use a smart pointer
   obj1.AddObject(&obj2);

   return 0;
}

2 Answers 2

10

There is no problem using raw pointers to, well, point to things—provided that you know the lifetimes of the objects you’re pointing to, and that you have some externally provided guarantee that the objects won’t expire while you’re referring to them.

What raw pointers should not be used for is ownership, because their type conveys nothing about ownership or lifetime semantics.

Most of the time, you can use a unique_ptr to own an object, and references or raw pointers to borrow from it; in the rarer case that you need shared ownership, you can use a shared_ptr with weak_ptr for borrowing.

A bit more on lifetimes. Say you have something like:

struct Base { virtual ~Base() {} };
struct Derived : Base {};

vector<Base*> bases;
Base* base = new Base();
bases.push_back(base);
delete base;
function(dynamic_cast<Derived&>(*bases[0]));  // won't necessarily throw

Dereferencing a pointer whose referent has been deleted is undefined, which is exactly what’s happening here. The memory to which it referred may still contain a valid object, or it may not—you can’t rely on any particular behaviour. Whereas if you had this:

vector<shared_ptr<Base>> bases;
shared_ptr<Base> base(make_shared<Base>());
bases.push_back(base);
base.reset();
function(dynamic_cast<Derived&>(*v[0]));  // will throw

The vector’s shared_ptr retains the object, making it safe to dereference even after base expires in the reset() call. It’s safe to use a raw pointer only when you know that the object will be alive when you dereference the pointer:

vector<unique_ptr<Base>> bases;
bases.emplace_back(new Base());
bases.emplace_back(new Derived());
{
  vector<Derived*> deriveds;
  for (const auto& base : bases)
    if (const auto derived = dynamic_cast<Derived*>(base.get()))
      deriveds.push_back(derived);
  function(deriveds);  // safe; lifetimes guaranteed by 'bases'
}
4
  • In another example I have I store derived classes in a list of base class pointers. When I want to retrieve them I return them by reference so I use a dynamic_cast and the function is templated so as to get the actual derived class. If the object goes out of scope this throws a bad cast error which is perfect. This is telling me that the pointer is no longer valid which is great! The example I have here is bad because even a cast to return a reference won't throw and error.
    – user441521
    May 16, 2014 at 20:51
  • 2
    @user441521: That sounds like you’re relying on undefined behaviour. You can’t test the validity of a raw pointer, and dereferencing a pointer to an expired object may do anything. If you run your program under a memory checker such as Valgrind, it will report an invalid memory access. Besides, you can just as easily use runtime polymorphism with smart pointers whose lifetime you do know: dynamic_cast<T&>(*p).
    – Jon Purdy
    May 17, 2014 at 0:52
  • Can you explain why the casting error would be an undefined behavior? The fact that it throws this error every time seems like it's a defined behavior.
    – user441521
    May 19, 2014 at 13:41
  • @user441521: I’ve updated my answer with more information about lifetimes and borrowing.
    – Jon Purdy
    May 19, 2014 at 13:58
1

By doing this, you have effectively tied these two objects together. You will now have to ensure that any time you destroy obj1 either explicitly or implicitly, you also destroy obj2. You've created a data structure where all elements have to be destroyed simultaneously.

If you use something like shared_pointer, you can get the same ease without this problem. You'll be able to do the same thing, letting scope clean up for you, but you don't have to worry about things going to hell if you later realize it makes sense to create objects in different scopes.

Is this really harder?

class Object
{
private:
   shared_ptr<Object> object;
public:
   void Object()
   void AddObject(shared_ptr<Object> obj)
   {
      object = obj;
   }

   shared_ptr<Object> GetObject()
   {
      return object;
   }
};


int main()
{
   shared_ptr<Object> obj1(new Object);
   shared_ptr<Object> obj2(new Object);

   obj1.AddObject(&obj2);

   return 0;
}

It is essentially the same as your code, but without the requirement to ensure that obj1 and obj2 share scope.

2
  • They don't have to share the same scope per se. obj2 could have come from some other class or function as long as it came by reference it can still be passed in. The way I understand pointer issues it's almost always an issue of scope, which is why we have these templated classes that act as local scope variables to wrap the pointers up. If it's about question of scope then it seems we could control that most of the time by passing references around. If we need a dynamic amount of something could we not store them as non pointers in a container and pass their references around when needed?
    – user441521
    May 16, 2014 at 20:46
  • My example here is a bad one. In another example if I'm storing a derived class and return a reference with a templated class that dynamic_casts to the derived type and the object goes out of scope, it throws a bad cast error. Seems like a handy way to avoid issues since I can catch that error and it's telling me the pointer is no longer valid.
    – user441521
    May 16, 2014 at 20:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.