3

I can understand that boost::shared_ptr doesn't validate for NULL before calling a custom deleter function, but how can I achieve this? This will help me avoid writing dumb wrappers for fclose or any function that doesn't (rightly) specify the behaviour.

My boost: #define BOOST_VERSION 104500. This is not C++ 11 (since I use boost).

The question is related to: make shared_ptr not use delete

Sample code:

static inline
FILE* safe_fopen(const char* filename, const char* mode)
{
      FILE* file = NULL;
      (void)fopen_s(&file, filename, mode);
      return file;
}

static inline
void safe_fclose(FILE* file)
{
      if (file)
         BOOST_VERIFY(0 == fclose(file));
}

...

boost::shared_ptr<FILE> file( safe_fopen(FILE_DOWNLOAD, "rb"), safe_fclose);
...
//    now it is created => open it once again
file.reset( safe_fopen(FILE_DOWNLOAD, "rb"), safe_fclose);

EDIT

My question initially had a second part concerning the use of shared_ptr: why providing the deleter as a function parameter instead of a template parameter? Apparently, the answer is here: Why does unique_ptr take two template parameters when shared_ptr only takes one? C++ 11 answer is unique_ptr, but why boost did't provide one - we'll never know.

Community
  • 1
  • 1
Liviu
  • 1,859
  • 2
  • 22
  • 48
  • 1
    The second question is very deep and essential to the design of `shared_ptr`. I recommend moving it a separate question (which presumably we can then close as a duplicate). – Kerrek SB Jan 15 '15 at 12:05
  • I would suggest to look at [std::shared_ptr](http://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr) if possible. – user3159253 Jan 15 '15 at 12:07
  • @Kerrek SB If I move it myself, wouldn't be the first question (left) as too dumb? I already focused all my energy into this one :p – Liviu Jan 15 '15 at 12:10
  • 1
    For the second question look at https://stackoverflow.com/questions/21355037/why-does-unique-ptr-take-two-template-parameters-when-shared-ptr-only-takes-one/21355146#21355146 – Wojtek Surowka Jan 15 '15 at 12:14
  • @Wojtek Surowka C++ 11 offers two versions, I understand, it doesn't seem to be the case with boost. – Liviu Jan 15 '15 at 12:27
  • @Liviu Still the answer to the question - why function parameter and not template parameter - is the same for `boost::shared_ptr` and `std::shared_ptr`. – Wojtek Surowka Jan 15 '15 at 12:52
  • 1
    I went ahead and marked the dupe. I think it (likely) answers the whole question. Of course, OP is free to edit/post a separate question for the parts not addressed – sehe Jan 15 '15 at 14:03
  • The question of why `shared_ptr` doesn't check for null while `unique_ptr` does check for null (and what this means for creating one from the other!) is perfectly legitimate, and in fact subject of a LWG issue. – Kerrek SB Jan 15 '15 at 14:29
  • If you use `fstream` instead of `FILE`, the destructor handles closing automatically. – Julian Jan 15 '15 at 15:38
  • @AtlasC1 this one needs `FILE` :D : `boost::shared_ptr curl( curl_easy_init(), curl_easy_cleanup);` – Liviu Jan 15 '15 at 17:22

2 Answers2

1

It appears to be about the difference between an empty (non-owning) shared_ptr and a null-ed one.

Nulled ones will (and should) invoke the deleter (it might mean something for the particular type of resource handle. In fact, the value "0" might not even be special).

Empty ones will invoke the deleter, but with a nullptr_t argument. Therefore, you could generically make a wrapper that wraps any adhoc deleter (be it an inline lambda, or a function pointer like &::free):

template <typename F>
OptionalDeleter<F> make_optional_deleter(F&& f) { return OptionalDeleter<F>(std::forward<F>(f)); }

int main() {
    auto d = make_optional_deleter([](void*){std::cout << "Deleting\n";});

    using namespace std;
    {
        shared_ptr<int> empty(std::nullptr_t{}, d);
    } // deleter not called, empty

    {
        shared_ptr<int> thing(static_cast<int*>(nullptr), d);
    } // deleter called, thing not empty
}

Will print

Empty, not deleting
Deleting

It looks like you get very "pure", faithful, pass-through of the original intent to the underlying API. This is a GoodThing™ since if shared_ptr<> made "arbitrary" exceptions, it would become unusable in cases where the deletion of a "NULL" (or default) value would be meaningful and should not be skipped.

Right now, the choice is yours, as it should be.

Of course, you can use a very similar wrapping stragegy like I just showed to generically skip the underlying API call if the resource handle value is "NULL" or otherwise invalid.

Live On Coliru

#include <memory>
#include <iostream>

template<typename F>
struct OptionalDeleter final {
    explicit OptionalDeleter(F&& f) : _f(std::forward<F>(f)) {}

    template <typename... Ts>
        void operator()(Ts&&... args) const { _f(std::forward<Ts>(args)...); }

    void operator()(std::nullptr_t)   const { std::cout << "Empty, not deleting\n"; }
  private:
    F _f;
};

template <typename F>
OptionalDeleter<F> make_optional_deleter(F&& f) { return OptionalDeleter<F>(std::forward<F>(f)); }

int main() {
    auto d = make_optional_deleter([](void*){std::cout << "Deleting\n";});

    using namespace std;
    {
        shared_ptr<int> empty(std::nullptr_t{}, d);
    } // deleter not called, empty

    {
        shared_ptr<int> thing(static_cast<int*>(nullptr), d);
    } // deleter called, thing not empty
}
sehe
  • 374,641
  • 47
  • 450
  • 633
1

One thought was perhaps instead of trying to protect the destructor, you should force the failure at construction, thus avoiding having to check an invalid during destruction.

static inline
FILE* safe_fopen(const char* filename, const char* mode)
{
      FILE* file = NULL;
      (void)fopen_s(&file, filename, mode);
      if (!file)
         throw std::exception(...); // check errno
      return file;
}

However this doesn't help fix an un-initialized boost::shared_ptr if that where to still happen for some reason.

Unlike make shared_ptr not use delete I think due to the nature of the functions you are just stuck with having these longer wrappers.

Community
  • 1
  • 1
Greg Domjan
  • 13,943
  • 6
  • 43
  • 59
  • So it is impossible to use the technique from @sehe's answer in `C++03` ? A template wrapper around a `void Func(T*)` ? – Liviu Jan 20 '15 at 09:35
  • @sehe's `OptionalDeleter` is using techniques not available to the earlier compiler version - as far as I'm aware. You could certainly chain together template constructs to do some of it indirectly if you found yourself having to do this with many types, but at some point consider is it worth the effort and complexity. Better to move up to C++11. – Greg Domjan Jan 21 '15 at 23:32
  • "Better to move up to C++11" Is it free? If so, I can recommend it to my company (for those wrappers)! – Liviu Jan 22 '15 at 07:59
  • What compiler are you using now? You can get gcc or visual studio express or... the list could be long of tools that are free of $ cost. That doesn't mean free of time/effort cost in updating code to new standard. – Greg Domjan Jan 22 '15 at 23:15