Fewnity
Fewnity

Reputation: 39

Dangling pointer issue C++

I'm making a game engine and I have run into a problem with destroying elements.

For example I have some cameras that follow and look at an target (a gameobject or an actor). Later in the game the camera target is destroyed and the camera code will crash because the code will try to access to a freed object and I want to avoid this crash.

So here is a simplified code example:
I have an instantiated object, an int for example, to keep things simple. I have several references to this object. If the instantiated object is deleted, I'd like to be able to check if the object still exists or not if one of my scripts wants to access to the destroyed object.

int* refToMyInt = nullptr; //I want to possibility to have empty pointer
int* refToMyInt2 = nullptr;

int* myInt = new int(2);
refToMyInt = myInt;
refToMyInt2 = refToMyInt;

delete myInt;

if(refToMyInt == nullptr && refToMyInt2 == nullptr)
  std::cout << "myInt Deleted!" << std::endl; // Never called

Except that it doesn't work, delete doesn't seem to change the variables to nullptr.
I don't want to manually set refToMyInt and refToMyInt2 to nullptr because I could have dozens of references to that object.
I've found a solution but it was to use weak_ptr and I'd like to avoid using it because using the lock() function to access the values each time is a bit cumbersome and impractical...

Is there another solution? Thank you!

Upvotes: -2

Views: 149

Answers (2)

Toggy Smith
Toggy Smith

Reputation: 340

"I'd like to avoid using it because using the lock() function to access the values each time is a bit cumbersome and impractical."

This might be true for *(ptr.lock()) vs *ptr but this is a false comparison. You should always check if ptr is a nullptr before dereferencing it:

if (ptr != nullptr) {
  auto value = *ptr;
}

and that is only marginally more verbose than:

if (auto sptr = ptr.lock()) {
  auto value = *sptr;
}

Answer:

The solution is a combination of std::shared_ptr and std::weak_ptr.

Instead of:

int* refToMyInt = nullptr; //I want to possibility to have empty pointer
int* refToMyInt2 = nullptr;

int* myInt = new int(2);
refToMyInt = myInt;
refToMyInt2 = refToMyInt;

delete myInt;

if(refToMyInt == nullptr && refToMyInt2 == nullptr)
  std::cout << "myInt Deleted!" << std::endl; // Never called

you can write:

std::weak_ptr<int> refToMyInt;
std::weak_ptr<int> refToMyInt2;

std::shared_ptr<int> myInt{ new int(2) };

refToMyInt = std::weak_ptr<int>(myInt);
refToMyInt2 = std::weak_ptr<int>(myInt);

myInt.reset();

if(refToMyInt.expired() && refToMyInt2.expired())
  std::cout << "myInt Deleted!" << std::endl; // Never called

I kept the original variable names to make comparisons easier.

Upvotes: 0

Pepijn Kramer
Pepijn Kramer

Reputation: 13076

These examples will show you a bit more of the intended use of vector/make_unique.

#include <memory>
#include <iostream>
#include <vector>

// dynamic memory allocation is mostly only necessary
// for polymorphic classes (e.g. classes derived from
// an abstract baseclass)

class my_interface
{
public:
    virtual void do_something() = 0;
    virtual ~my_interface() = default;
protected:
    my_interface() = default;
};

// 

class my_concrete_class :
    public my_interface
{
public:
    my_concrete_class() = default;

    // to show my_concrete_class 
    // instance will be deleted by smart pointer.
    ~my_concrete_class()
    {
        std::cout << "my_concrete_class destructor\n";
    }


    void do_something() override
    {
    }

    
};

int main()
{
    int my_int{ 1 };
    int& ref_to_my_int{ my_int }; // no & this ensure a ref is always refering to a valid object
    int* ptr_to_my_int{ &my_int }; // non-owning pointer https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#r3-a-raw-pointer-a-t-is-non-owning

    ref_to_my_int = 42; // modify through reference
    std::cout << "my_int now has value " << my_int;

    // create a scope that determines life cycle of my_concrete_class.
    {
        std::cout << "entering scope 1\n";
        std::unique_ptr<my_interface> itf_ptr{ std::make_unique<my_concrete_class>() };
        std::cout << "going out of scope , unique_ptr will be destructed\n";
    }

    {
        std::vector<int> values{ 1,2,3 }; // will dynamically allocate memory for 3 ints 
        std::cout << "vector going out of scope, will delete allocated memory\n";
    }

    return 0;
}

Upvotes: -1

Related Questions