Smart pointers with a library written in C
Asked Answered
P

3

3

I'm using C++ with the OpenCV library, which is a library image-processing although that's not relevant for this question. Currently I have a design decision to make.

OpenCV, being a C library, has its data structures (such as CvMat) declared as structs. To create them, you use functions like cvCreateMat, and to release them, you use functions like cvReleaseMat. Being a C++ programmer, I created a special cv_scoped class which would automatically call cvReleaseMat when it went out of scope (like boost::scoped_ptr).

What I'm realising now is that I wish I could use auto_ptr and shared_ptr in cases as well. I just feel that writing code for my own cv_auto_ptr and cv_shared_ptr classes would be a bad idea, not to mention a waste of time. So I've been looking for solutions, and I've come up with three possibilities.

First, I could use the cv_scoped class I've already made. I'd rename it to cv_ptr and then use smart pointers like so: std::auto_ptr<cv_ptr>. The annoying thing about this though is, I'd always have to dereference twice:

std::auto_ptr<cv_ptr> matrix(cv_ptr(cvCreateMat(320, 240, CV_32FC3)));
cvPow(matrix.get()->get()); // one get for the auto_ptr, one for the cv_ptr

I know it looks like I could declare an implicit conversion, but I couldn't actually - most of OpenCV's functions have the parameter void* - so no implicit conversion would be called. I would really like a way of doing this where I didn't have to do the double dereference.

Second, I could somehow override operator delete. I don't want to override the global operator delete because I'd only want this to apply to CvMat (and a few other) types. However, I can't change the library, so I can't add operator delete to the CvMat struct. So I don't know how this would work.

Third, I could just rewrite my own auto_ptr, scoped_ptr, and shared_ptr. They're not large classes so it wouldn't be too difficult, but I just feel like this is bad design. If I were to do this, I would probably do something along these lines:

class cv_auto_ptr {
public:
  cv_auto_ptr();
  ~cv_auto_ptr();

  // each method would just be a proxy for the smart pointer
  CvMat* get() { return this->matrix_.get()->get(); }
  // all the other operators/methods in auto_ptr would be the same, you get the idea

private:
  auto_ptr<cv_ptr> matrix_; // cv_ptr deletes CvMat properly
}

What would you do in my situation? Please help me figure this one out.

Posthumous answered 31/1, 2009 at 9:44 Comment(0)
P
4

The auto_ptr are really designed for RAII on C++ class with constructs/destructors you are pushing their uses here to things they probably should not be used for (but can).

Anyway don'y you want to be able to use your C++ object as if it was a normal stack variable without dynamically allocating each time?

The standard solution to your problem is to create a wrapper with constructor/destructor.
But to make it usable by the C functions just add an internal cast operator so it auto-magically converts itself back to the C object when passed to a C function

Write a wrapper class.

class Mat
{
    CvMat* impl;
    public:
        Mat(/* Constructor  Arguments */)
        {
            impl = cvCreateMat(/* BLAH */);
        }
        ~Mat()
        {
            cvReleaseMat(impl);
        }
        operator CvMat*()
        {   // Cast opertator. Convert your C++ wrapper object into C object
            // when you use it with all those C functions that come with the
            // library.

            return impl;
        }
};

void Plop(CvMat* x)
{   // Some C function dealing with CvMat
}

int main()
{                            // Don't need to dynamically allocate
    Mat                  m;  // Just create on the stack.
    Plop(m);                 // Call Plop directly

    std::auto_ptr<Mat>   mP(new Mat);
    Plop(*mP);
}
Pound answered 31/1, 2009 at 17:16 Comment(1)
I thought about this for about 15 minutes and I think you might be onto the best solution here. The differences from this and the other solutions are subtle yet very important. I'm going to think about it a bit longer though before accepting this answer.Posthumous
S
6

One approach that you could consider is to used the fact that std::tr1::shared_ptr has the functionality to provide a custom deleter. I have no familiarity with OpenCV so I'm inferring from what you've written.

struct CvMatDeleter
{
    void operator( CvMat* p ) { cvReleaseMat( p ) ; }
};

void test()
{
    std::tr1::shared_ptr< CvMat > pMat( cvCreateMat(320, 240, CV_32FC3), CvMatDeleter() );
    // . . .
}

Because the deleter is store in the shared pointer you can just use it as normal and when the shared raw pointer finally needs to be deleted, cvReleaseMat will be called as required. Note that auto_ptr and scoped_ptr are much lighter classes so don't have the functionality for custom deleters, but if you're prepared for the small overhead then shared_ptr can be used in their place.

Strongarm answered 31/1, 2009 at 10:18 Comment(6)
Wow that sounds like exactly what I need, for the shared_ptr at least. Thanks!Posthumous
hmm why the functor? why not pass cvReleaseMat as a deleter directly?Recrimination
No real reason; habit I suppose. In some cases it can be slightly more efficient to use a functor as its body can be inlined at the point of use. In this case, I doubt it makes any difference at all.Strongarm
right. can you elaborate on the functor being more efficient? I don't understand why it's easier to inline than a function pointerRecrimination
Basically for a function pointer, its type only encapsulates its signature, but a functor's type encapsulates its behaviour as well. At compile time, if you are a function that is given a function pointer, you have to call it to effect its behaviour, whereas a functor's operator() may be known.Strongarm
In OpenCV's case, the functor object is needed anyway because cvReleaseMat is void cvReleaseMat(CvMat** p) (it sets the pointer to NULL after deleting)Supervisory
P
4

The auto_ptr are really designed for RAII on C++ class with constructs/destructors you are pushing their uses here to things they probably should not be used for (but can).

Anyway don'y you want to be able to use your C++ object as if it was a normal stack variable without dynamically allocating each time?

The standard solution to your problem is to create a wrapper with constructor/destructor.
But to make it usable by the C functions just add an internal cast operator so it auto-magically converts itself back to the C object when passed to a C function

Write a wrapper class.

class Mat
{
    CvMat* impl;
    public:
        Mat(/* Constructor  Arguments */)
        {
            impl = cvCreateMat(/* BLAH */);
        }
        ~Mat()
        {
            cvReleaseMat(impl);
        }
        operator CvMat*()
        {   // Cast opertator. Convert your C++ wrapper object into C object
            // when you use it with all those C functions that come with the
            // library.

            return impl;
        }
};

void Plop(CvMat* x)
{   // Some C function dealing with CvMat
}

int main()
{                            // Don't need to dynamically allocate
    Mat                  m;  // Just create on the stack.
    Plop(m);                 // Call Plop directly

    std::auto_ptr<Mat>   mP(new Mat);
    Plop(*mP);
}
Pound answered 31/1, 2009 at 17:16 Comment(1)
I thought about this for about 15 minutes and I think you might be onto the best solution here. The differences from this and the other solutions are subtle yet very important. I'm going to think about it a bit longer though before accepting this answer.Posthumous
R
3

If all you care about is exception safety, do this everytime you use matrixes:

void f() {
    try {
        CvMat* mat = cvCreateMat(320, 240, CV_32FC3));
        // ...
    } catch(...) {
        cvReleaseMat(mat);
        throw;
    }
    cvReleaseMat(mat);
}

If, on the other hand, you want a sane solution, go the extra mile and write a full wrapper.

namespace cv {

class Mat {
public:
    enum Type { /* ... */ };
    Mat(int w, int h, Type type) {
        impl = cvCreateMat(w, h, intFromType(type));
    }

    ~Mat() {
        cvReleaseMat(impl);
    }

    void pow() { // wrap all operations
        cvPow(impl);
    }

private:
    CvMat* impl;
};

}

Going the middle way, using a hodge podge of generic smart pointers and "cv_ptrs" sounds like a recipe for headaches and an unnecessary complication.

Recrimination answered 31/1, 2009 at 10:14 Comment(1)
I see your point but I think I would probably go for my own smart pointers over this. I think because there are so many different functions and types, I feel like I'm reinventing the wheel rewriting their signatures. Thanks though!Posthumous

© 2022 - 2024 — McMap. All rights reserved.