views:

74

answers:

2

I'm making a TextField class, which at the moment stores a reference to a variable, and whenever the programmer wants, they can call TextField.show() and the referenced variable will be read and displayed on-screen.

This however can result in problems if the reference becomes invalid. For example, if the variable referenced goes out of scope, I've got problems.

Basically, is there any way to ensure that the user is giving a reference that won't become invalid? Or, is there a better way to set up this system? Or if there isn't, what's the best way to handle invalid references in this case?

+5  A: 

If you use a reference, then it becomes the responsibility of the caller to ensure his/her code is not undefined. Basically, there is no way to know. You could use shared_ptr to ensure that this problem won't happen, and you allow shared ownership. That means if the variable(shared_ptr) goes out of scope, the value won't be destructed unless there is no one else is pointing to the value it self.

AraK
+1  A: 

With references, no. You can't tell, as C++ won't automatically let you know a reference is being destroyed.

If you switch to using pointers though, you do get a few options. One is to use shared_ptr, as already mentioned above. Another, which is useable if you know your TextField object will outlive the object(s) being displayed in it, is to make a wrapper class around the variable which will take a pointer to your TextField and notify the text field in the wrapper's destructor.

For example, here is a wrapper class that would notify your TextField if a std::string went out of scope.

In the wrapper class header file:

// Wraps a pointer to a string, which TextField(s) will use.
// Notify the fields using it when it goes out of scope.
class WatchedPtr
{ string * ptrS_;
  std::set<TextField*> watchers_;
  static void NotifyOfDeath(TextField *watcher);
  WatchedPtr(const WatchedPtr&);            // Prevent copying.
  WatchedPtr& operator=(const WatchedPtr&); // Prevent copying.
public:
  WatchedPtr(string *s) : ptrS_(s) {}
  void addWatcher   (TextField *watcher) { watchers_.insert(watcher);  }
  void removeWatcher(TextField *watcher) { watchers_.erase(watcher); }
  ~WatchedPtr()   
  { std::for_each(watchers_.begin(), watchers_.end(), NotifyOfDeath); 
    delete ptrS_;
    ptrS_ = NULL;
  }
  string* get() { return ptrS_; }
};

In the wrapper class cpp file:

void WatchedPtr::NotifyOfDeath(TextField *watcher) 
{ if(watcher) 
  {  watcher->clientIsDead(); 
  } 
}

In the TextField class header file:

class TextField
{
  string *text_;
public:
  TextField() : text_(NULL) {}

  void SetTextRef(WatchedPtr &wp)
  { text_ = wp.get();
    wp.addWatcher(this);
  }

  void show()
  { if(text_)
      { cout << "Drawing textfield with text [" << (*text_) << "]" << endl;
      }
    else
      { cout << "String is not valid. Drawing empty text box." << endl;
      }
  }

  void clientIsDead() { text_ = NULL; } // do not trust the pointer anymore.
};

An example of use:

int main(int argc, char* *argv)
{
  TextField tf;
  { WatchedPtr ptrString(new string("Some Text"));
    tf.SetTextRef(ptrString);

    tf.show();  // uses the string.
  }             // ptrString's destructor tells TextField to stop trusting the pointer.
  tf.show();    // string is not used.
  return 0;
}

This is a bit brittle though, and not as flexible as a full shared pointer, but could be useful if you don't have access to shared_ptr. It will fail if your TextField is destroyed while the WatchedPtr still exists, as the callback pointer will be invalid when WatchedPtr tries to use it in the destructor. However it would work in many examples, e.g. a status bar text that many items update, or the text in a progress bar that many objects could change.

(This is a simplified example, to get the point across. It could be improved in many ways, such as templating the WatchedPtr so it works for other classes than strings, passing a function-object or pointer so the watcher can be used with other classes than TextField etc.)

Pat Wallace