3

I am interacting with (not-owned-by-me) API that takes a raw pointer to Thing and sometimes deletes it on its own, basically something like:

bool foo(Thing* ptr) {
    if (/* some condition */) {
        delete ptr;
        return true;
    } else {
        return false;
    }
}

Now I'm writing some tests that on a side interact with this method, and I would want to avoid writing typical T* ptr = new Thing(); bool res = foo(ptr); /* real test */; if (!res) { delete ptr; }; spread across the code.

So I've created some kind of "tracking-wrapper" that looks like this:

template <typename T> class Holder {
public:

    // Subclass of Z where the dtor will just update Holder's state.
    template <typename Z> class Helper : public Z {
    public:
        Helper(Holder<Z>& holder): holder_{holder} {}
        virtual ~Helper() { holder_.markDeleted(); }

    private:
        Holder<Z>& holder_;
    };

    ~Holder() {
        if (!deleted_) { delete ptr; }
    }

    void markDeleted() { deleted_ = true; }
    T* data() { return ptr; }

private:
    bool deleted_ = false;
    T* ptr = new Helper<T>(*this);
};

so basically I can use it like this:

Holder<Thing> h;
foo(h.data());

with Holder's destructor doing the cleanup of Thing* if necessary.

Is there any alternative for this custom code (e.g. in standard library)?

We can assume the following:

  • T's destructor is virtual,
  • T does not require arguments to be constructed,
  • we cannot modify T.
Adam Kotwasinski
  • 4,377
  • 3
  • 17
  • 40
  • 1
    If you have control over the type. You could derive thing and make it set a on deleted function pointer (in the constructor, or the site where your object is new'd) to some function in your codebase, then call that function pointer from the destructor. – George Sep 24 '21 at 22:10
  • I don't. `Thing` is given to me. I'll add it to question. – Adam Kotwasinski Sep 24 '21 at 22:11
  • There's nothing in C++ that will trigger some kind of an action when a raw pointer gets deleted (aside from the pointed object's destructor). C++ does not work like that. C++ is not managed code. In situation that involves this kind of a badly designed API, the usual solution is to put a facade on it. You never call `foo()` directly, but your own function that does all the necessary cleanup. – Sam Varshavchik Sep 24 '21 at 22:12
  • 2
    At first glance, you are asking for `std::unique_ptr` or `std::shared_ptr`. – François Andrieux Sep 24 '21 at 22:15
  • @FrançoisAndrieux No, because then I'd effectively have something like `std::unique_ptr ptr = std::make_unique(); delete ptr.get();`; with then unique_ptr's destructor trying to delete the same thing again. – Adam Kotwasinski Sep 24 '21 at 22:18
  • @AdamKotwasinski Then there is no solution to what you are asking. The only way to react or know if an object is destroyed is via its destructor. So if `Think` doesn't manage it, there is nothing you can do. There is a remote possibility via overloading the global delete operator, but regardless of your needs, it is not a good idea. – François Andrieux Sep 24 '21 at 22:29
  • If `Thing` adds an instance pointer to some `static Thing::set sAllTheThings;` on construction, and removes the entry from there on destruction, you could query `sAllTheThings` to see if the raw pointer was deleted. – Eljay Sep 24 '21 at 22:31
  • Look up design pattern "facade." I don't think you can do that with raw pointers, but I notice that the interface tells you whether it deleted the pointer, by the return value and rules hidden in the documentation and/or code. I'd just replace it with a facade that handles those for you, and deletes it if it needs to. – Kenny Ostrom Sep 27 '21 at 17:30
  • Yeah, actually the example code comes from facade code, and I can live with that (i.e. leave the if-then-delete check) - just was wondering if there's something that could be done "cleaner". – Adam Kotwasinski Sep 27 '21 at 19:29
  • 1
    Does this answer your question? [How do I use a custom deleter with a std::unique\_ptr member?](https://stackoverflow.com/questions/19053351/how-do-i-use-a-custom-deleter-with-a-stdunique-ptr-member) – Mikhail Sep 27 '21 at 21:55
  • @Mikhail unfortunately not, in that case we'd have API that consumer unique_ptr, and not a raw pointer; I guess that my question has no good answer, so it's very custom (but still conceptually "simple") code. – Adam Kotwasinski Sep 27 '21 at 22:10

1 Answers1

0
auto h = std::make_unique<T>();
if (foo(h.get())) {
   h.release(); // already deleted, so we don't own it
}

(And this is why implementations shouldn't mark unique_ptr::release as [[nodiscard]])

Alex Guteniev
  • 12,039
  • 2
  • 34
  • 79
  • 1
    except [`release`](https://en.cppreference.com/w/cpp/memory/unique_ptr/release) doesn't delete the pointer... so it's absolutely `[[nodiscard]]` because otherwise this is a memory leak, because someone needs to take ownership. If you want to delete the pointer you're looking for [`reset`](https://en.cppreference.com/w/cpp/memory/unique_ptr/reset). Also note: Deleting the pointer held by a unique pointer without first taking ownership is undefined behavior. – Mgetz Sep 27 '21 at 19:10
  • @Mgetz, can you prove the claim about undefined behavior? – Alex Guteniev Sep 27 '21 at 19:33
  • It's a use after free/ double delete, the `unique_ptr` still believes it's live even though it's holding onto a dead value. That's classic undefined behavior. While your code may not directly trigger nasal demons it is encouraging them. If any exception happens between the `delete` and the call to `release` that's going to cause UB if the `unique_ptr` destructor gets called. To fix your code you directly need to `release` into `foo`. Which hands ownership to the deleting function. But `unique_ptr` has a strict ownership semantics. – Mgetz Sep 27 '21 at 19:42
  • So if the code is exception safe, and doesn't throw exceptions when it shouldn't, strict ownership semantic is respected, and there's no UB – Alex Guteniev Sep 27 '21 at 19:46
  • Except you're not, strict ownership means either you must call `release` **prior** to deleting (e.g. taking ownership) or you must let the `unique_ptr` destroy the object. Destroying the object via the value returned from `get` is undefined behavior because it violates strict ownership – Mgetz Sep 27 '21 at 19:51
  • Nope, standard does not dictate how exactly `unique_ptr` should be used. It only gives a possible use in Note (which is non-normative, and says "the uses include"). It also specifies that `release`returns exactly the value of `get` prior the release – Alex Guteniev Sep 27 '21 at 20:03