0

I have a not-so-ideal situation where a class returns handle references to objects that shouldn't be accessed after the parent objects' lifetimes. What is the best way to alter the pattern below to aid defensive coding?

// 'A<T>' is a thin factory-style utility class with asynchronous consumers.
template <typename T>
struct A {
  A() : h_(*(new T())) { /* ... */ }
  ~A() { /* h_ deleted or ownership passed elsewhere */ }

  // What's the best way to indicate that these handles shouldn't be used after
  // the destructions of the A instances?
  T &handle() { return h_; }

private
  T &h_;
};

struct B { /* ... */ };

int main() {
   B *b1{nullptr};
   {
     A<B> a;

     // Is there a good way to trigger detection that the reference is bound to
     // a variable which will outlive its 'valid' local lifetime?
     b1 = &a.handle();

     B &b2(a.handle()); // this is reasonable though

     b1->ok_action();
     b2.also_alright();
   }
   b1->uh_oh();
}

I know you can't truly prevent a user of C++ from doing most unsafe things, but if I could at least generate warnings on trivial accidental uses like this is would be the bulk of what I'd like to achieve.

Jeff
  • 3,475
  • 4
  • 26
  • 35
  • In understand this is existing code, but I'm not sure what you must keep and what you can change. I'd say you should stop saving the result of `handle()` to pointers or references. Either use it directly (e.g. `a.handle().methodOfB()` or copy it `B b=a.handle()` (provided B has the right copy semantics) – jsantander Apr 08 '14 at 04:38
  • @jsantander A<> is a sort of lifetime manager wrapper for objects that will be mucked around with at the point of wrapper destruction, so copy semantics do not apply unfortunately. Always calling `a.handle.methodOfB()` is definitely feasible, but I'd like to add build-time detection of saving a reference with a longer lifetime than the wrapper, to make misusing this admittedly tricky interface a bit harder. – Jeff Apr 08 '14 at 04:44
  • Ideally you should be using std::weak_ptr instead of these references. – berkus Apr 08 '14 at 05:57

2 Answers2

1

I'm taking the liberty of making a few assumptions about your situation:

  • The handles point to dynamically allocated objects generated by A at the users discretion.
  • The handles will be passed around where A is out of scope, thus A cannot be used as a mandatory gateway.
  • The data to which the handles point must be destroyed when A is destroyed, thus automatic garbage collection cannot be applied to the handles.
  • So far, compile-time safety checking does not seem to be possible. You want coding mistakes to manifest themselves at runtime through some kind of exception mechanism rather than spontaneous crashes.

With that in mind here's a possible solution:

Within A's constructor, allocate some kind of signal object S which is set when A is destroyed. Handle S using a shared_ptr. Have A::handle return a custom handle class H which contains a B handle, and a shared_ptr to S. Create a dereference operator within H which verifies that A is still valid (S is not set), or throws an exception. When all handles expire, S will be destroyed automatically.

glank
  • 371
  • 2
  • 10
0

You want object A to produce another object of class B, let somebody use it, then ensure that B is destroyed before A?

Rather than return an instance of B, would it be possible to define a method on A which obtains the B and then passes it to some kind of delegate (virtual method, functor, lambda function)? This way the user function is nested inside a call to a method on the A object, so it's logically impossible for A to be destroyed before the user code has finished whatever it is doing.

For instance:

class A { public: template <typename Functor> void DoSomethingWithAnInstanceOfB(const char* whichB, Functor f) { B& bref = InternalLookupB(whichB); f(bref); } };

This looks up the correct B instance and then passes it to an arbitrary functor. The functor can do whatever it wants, but it necessarily must return before DoSomethingWithAnInstanceOfB() will return, therefore guaranteeing that A's lifetime is at least as long as B.

brewbuck
  • 887
  • 6
  • 9
  • It's unfortunately not going to be feasible to do dynamic lookup for this management template class. `A<>` basically acts as a factory for asynchronous recipients, so the underlying objects can't be safely modified or even consistently read after the ownership passes in the `~A()` call. `A` can't extend `T` since the `Ts` themselves can't live in the stack, and making a slew of functors/function typedefs is not realistic, since leaving all the managed `T` classes alone and uncluttered is a high priority. I do appreciate your suggestion, though, even if I can't use it in this case. – Jeff Apr 08 '14 at 05:04