C++/CLI Wrapping a Function that Returns a std::shared_ptr
Asked Answered
M

2

7

I'm currently wrapping a C++ class with C++/CLI for .NET interoperability following the standard process of holding a native pointer in a managed class. In one instance, I have a native class that has a function like:

std::shared_ptr<BaseChannel> channelData(const int RunNumber);

I have already begun creating a wrapper class for BaseChannel. However, if I pass the raw pointer to the constructor of the managed class, there are no guarantees on the lifetime of the object being pointed to by the managed class. I.e. the shared_ptr could go out of scope and the object will get deleted and the managed class will be left holding a dangling pointer.

What is the common solution for this situation?

UPDATE

@Ben: So I wrap the class that holds the method in the above question like so (let's say it is in a native class called Node and it is being wrapped in a managed class called NodeRef:

ChannelUser^ NodeRef::ChannelData(int runNumber)
{
    // mpNode is native class pointer of type Node held in managed class
    // wrapper called NodeRef
    std::shared_ptr<BaseChannel> spBaseChannel = mpNode->channelData(runNumber);

    // ChannelUser is using clr_scoped_ptr to hold the shared_ptr
    ChannelUser^ channelUser = gcnew ChannelUser(spBaseChannel);
    return channelUser;
}

Because the shared_ptr does not have its reference count increased as it is passed to the managed class by reference, does that mean

that as long as this shared_ptr is in scope, the object it points to will still exist, because its reference count will be at least 1

? (ref C++ - passing references to std::shared_ptr or boost::shared_ptr)

Miscreant answered 30/5, 2011 at 4:47 Comment(2)
The reference count is increased (once by spBaseChannel's constructor, once by new shared_ptr in the ChannelUser ctor-initializer)... but this stuff isn't going to be thread safe when std::shared_ptr isn't.Paregmenon
@Ben: I updated the last question to indicate my concern properly. However, I think you've answered it in your previous comment anyway. The ref counter gets increased in the ChannelUser ctor-initializer.Miscreant
P
7

shared_ptr is a native type, and managed objects can't have integral native subobjects.

However, as you note, managed objects can have pointers to native objects. What you need is a pointer to a shared_ptr, which will count as a reference to the BaseChannel object and keep it from being freed prematurely.

Of course, there are lots of reasons to use a smart pointer instead of a raw shared_ptr<BaseChannel>*. I've written a smart pointer which should be suitable, you can find it on codereview.stackexchange.com: "scoped_ptr for C++/CLI (ensure managed object properly frees owned native object)"


Example (not compile tested):

ref class ChannelUser
{
    clr_scoped_ptr<shared_ptr<BaseChannel>> chan_ptr;

public:
    ChannelUser( shared_ptr<BaseChannel>& chan ) : chan_ptr(new shared_ptr<BaseChannel>(chan)) {}
};

This automatically implements IDisposable and deletes the shared_ptr when Dispose or the finalizer runs, which in turn reduces the reference count on the BaseChannel.

Paregmenon answered 30/5, 2011 at 5:22 Comment(2)
+1 - Can you provide an example of how to use your clr_scoped_ptr in your answer?Miscreant
@Seth: Have done, hope this helps. Let me know if you find any major errors.Paregmenon
F
18

Here's a managed shared_ptr<T>. You can assign to it directly from a shared_ptr and it'll take a copy that it will delete when the managed object is GC'd or disposed.

Examples:

m_shared_ptr<CupCake> cupCake0(new CupCake());
m_shared_ptr<CupCake> cupCake1 = new CupCake();
m_shared_ptr<CupCake> cupCake2 = shared_ptr<CupCake>(new CupCake());
m_shared_ptr<CupCake> cupCake3 = make_shared<CupCake>();
shared_ptr<CupCake> cupCake4 = (shared_ptr<CupCake>)cupCake3;

Code:

#pragma once

#include <memory>

template <class T>
public ref class m_shared_ptr sealed
{
    std::shared_ptr<T>* pPtr;

public:
    m_shared_ptr() 
        : pPtr(new std::shared_ptr<T>()) 
    {}

    m_shared_ptr(T* t) {
        pPtr = new std::shared_ptr<T>(t);
    }

    m_shared_ptr(std::shared_ptr<T> t) {
        pPtr = new std::shared_ptr<T>(t);
    }

    m_shared_ptr(const m_shared_ptr<T>% t) {
        pPtr = new std::shared_ptr<T>(*t.pPtr);
    }

    !m_shared_ptr() {
        delete pPtr;
    }

    ~m_shared_ptr() {
        delete pPtr;
    }

    operator std::shared_ptr<T>() {
        return *pPtr;
    }

    m_shared_ptr<T>% operator=(T* ptr) {
        delete pPtr;
        pPtr = new std::shared_ptr<T>(ptr);
        return *this;
    }

    T* operator->() {
        return (*pPtr).get();
    }

    void reset() {
        pPtr->reset();
    }
};
Facer answered 1/10, 2012 at 13:40 Comment(8)
awesome answer. very underrated.Malm
the assignment operator of m_shared_ptr isn't deleting p first, isn't that a memory leak?Halfassed
I think you're right feel free to edit and fix up this answerFacer
I came up with a few additional methods I needed : // Useful for accessing shared_ptr members like 'use_count' const std::shared_ptr<T> & get() { return *pPtr; } // Useful when passing to another managed object std::shared_ptr<T>* share() { return new std::shared_ptr<T>(*pPtr); } // Allows assignment via std::make_shared m_shared_ptr<T>% operator=(const std::shared_ptr<T>& t) { delete pPtr; pPtr = new std::shared_ptr<T>(t); return *this; } ~Roof
Brilliant was so about to write something like this myself. Thanks for saving me some time! :DPopular
Looks very good. I swapped this to use boost::shared_ptr but your last example doesn't work for me (reports it can't convert from m_shared_ptr<T> to shared_ptr<T>). Any ideas?Fuchsia
@JonCage Long time no see Mr Cage, afraid I'm not sure of the reason for the problem, perhaps need to be marked as an implicit cast operator. Haven't been using C++/CLI for a while now.Facer
OMG. It's you! Ha! ...well I'm not sure what I changed but it magically started working so I can only assume I'd done something stupid ;-)Fuchsia
P
7

shared_ptr is a native type, and managed objects can't have integral native subobjects.

However, as you note, managed objects can have pointers to native objects. What you need is a pointer to a shared_ptr, which will count as a reference to the BaseChannel object and keep it from being freed prematurely.

Of course, there are lots of reasons to use a smart pointer instead of a raw shared_ptr<BaseChannel>*. I've written a smart pointer which should be suitable, you can find it on codereview.stackexchange.com: "scoped_ptr for C++/CLI (ensure managed object properly frees owned native object)"


Example (not compile tested):

ref class ChannelUser
{
    clr_scoped_ptr<shared_ptr<BaseChannel>> chan_ptr;

public:
    ChannelUser( shared_ptr<BaseChannel>& chan ) : chan_ptr(new shared_ptr<BaseChannel>(chan)) {}
};

This automatically implements IDisposable and deletes the shared_ptr when Dispose or the finalizer runs, which in turn reduces the reference count on the BaseChannel.

Paregmenon answered 30/5, 2011 at 5:22 Comment(2)
+1 - Can you provide an example of how to use your clr_scoped_ptr in your answer?Miscreant
@Seth: Have done, hope this helps. Let me know if you find any major errors.Paregmenon

© 2022 - 2024 — McMap. All rights reserved.