Humam Helfawi
Humam Helfawi

Reputation: 20264

Pointer to stack object without ownership

I want to have a class with a pointer member variable. This pointer should point to an object which may be stack-allocated or heap-allocated. However, this pointer should not have any ownership. In other words, no delete should be called at all when the pointer goes out of scope. I think that a raw pointer could solve the problem... However, I am not sure if there is a better C++11 approach than raw pointers?

Example:

class foo{
public:
    bar* pntr
};

int main(){
    bar a;
    foo b;
    b.pntr=&a;
}

Upvotes: 15

Views: 6682

Answers (5)

Carlton
Carlton

Reputation: 4297

The problem with a raw pointer is that there's no way to tell if it still points to a valid object. Fortunately, std::shared_ptr has an aliasing constructor that you can use to effectively make a std::weak_ptr to a class member with automatic storage duration. Example:

#include <iostream>
#include <memory>

using namespace std;

struct A {
    int x;
};

void PrintValue(weak_ptr<int> wp) {
    if (auto sp = wp.lock())
        cout << *sp << endl;
    else
        cout << "Object is expired." << endl;
}

int main() {

    shared_ptr<A> a(new A);
    a->x = 42;
    weak_ptr<int> wpInt (shared_ptr<int>(a, &a->x));

    PrintValue(wpInt);
    a.reset();  //a->x has been destroyed, wpInt no longer points to a valid int
    PrintValue(wpInt);

    return 0;
}

Prints:

42

Object is expired.

The main benefit to this approach is that the weak_ptr does not prevent the object from going out of scope and being deleted, but at the same time it can safely detect when the object is no longer valid. The downsides are the increased overhead of the smart pointer, and the fact that you ultimately need a shared_ptr to an object. I.e. you can't do this exclusively with objects allocated on the stack.

Upvotes: 1

Noah
Noah

Reputation: 41

If by "better approach" you mean "safer approach", then yes, I've implemented a "non-owning" smart pointer here: https://github.com/duneroadrunner/SaferCPlusPlus. (Shameless plug alert, but I think it's relevant here.) So your code would look like this:

#include "mseregistered.h"
...

class foo{
public:
    mse::TRegisteredPointer<bar> pntr;
};

int main(){
    mse::TRegisteredObj<bar> a;
    foo b;
    b.pntr=&a;
}

TRegisteredPointer is "smarter" than raw pointers in that it knows when the target gets destroyed. For example:

int main(){
    foo b;
    bar c;
    {
        mse::TRegisteredObj<bar> a;
        b.pntr = &a;
        c = *(b.pntr);
    }
    try {
        c = *(b.pntr);
    } catch(...) {
        // b.pntr "knows" that the object it was pointing to has been deleted so it throws an exception. 
    };
}

TRegisteredPointer generally has lower performance cost than say, std::shared_ptr. Much lower when you have the opportunity to allocate the target object on the stack. It's still fairly new though and not well documented yet, but the library includes commented examples of it's use (in the file "msetl_example.cpp", the bottom half).

The library also provides TRegisteredPointerForLegacy, which is somewhat slower than TRegisteredPointer but can be used as a drop-in substitute for raw pointers in almost any situation. (In particular it can be used before the target type is completely defined, which is not the case with TRegisteredPointer.)

In terms of the sentiment of your question, I think it's valid. By now C++ programmers should at least have the option of avoiding unnecessary risk of invalid memory access. Raw pointers can be a valid option too, but I think it depends on the context. If it's a complex piece of software where security is more important than performance, a safer alternative might be better.

Upvotes: 0

vsoftco
vsoftco

Reputation: 56557

Raw pointers are perfectly fine here. C++11 doesn't have any other "dumb" smart pointer that deals with non-owning objects, so you cannot use C++11 smart pointers. There is a proposal for a "stupid" smart pointer for non-owned objects:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4282.pdf

already implemented experimentally as std::experimental::observer_ptr (thanks @T.C. for the hint).

Another alternative is to use a smart pointer with a custom deleter that doesn't do anything:

#include <memory>

int main()
{
    int a{42};

    auto no_op = [](int*){};
    std::unique_ptr<int, decltype(no_op)> up(&a, no_op);
}

or, as mentioned by @T.C. in the comment, a std::reference_wrapper.

As mentioned by @Lightness Races in Orbit, a std::weak_ptr may also be a solution, as the latter is also a non-owning smart pointer. However a std::weak_ptr can only be constructed from a std::shared_ptr or another std::weak_ptr. A serious downside is that the std::shared_ptr is a "heavy" object (because of the internal reference counting mechanism). Note that even in this case the std::shared_ptr must have a trivial custom deleter, otherwise it corrupts the stack for pointers to automatic variables.

Upvotes: 23

Ulrich Eckhardt
Ulrich Eckhardt

Reputation: 17415

Simply allocate the object dynamically and use a shared_ptr. Yes, it will actually delete the thing, but only if it's the last one with a reference. Further, it prevents others from deleting it, too. This is exactly the right thing to do, both to avoid memory leaks and dangling pointers. Also check out the related weap_ptr, which you could perhaps use to your advantage, too, if the lifetime requirements for the pointee are different.

Upvotes: -2

Felix Glas
Felix Glas

Reputation: 15524

Using a raw pointer is perfectly ok here as you don't intend to let the pointer have ownership of the resource pointed to.

Upvotes: 3

Related Questions