How can I prevent myself from using a parameter after I’ve extracted all value from it?

Raymond Chen

Suppose you have a function that takes a parameter that you want to transform in some way, and you want to require that all future access to the parameter be done through the transformed version. One example is a wrapper class that does logging.¹

struct WidgetRefWrapper
{
    WidgetRefWrapper(
        Widget& widget,
        Logger& logger) :
    m_widget(widget), m_logger(logger) {}
    
    void Toggle() try
    {
        m_logger.Log("Toggling the widget");
        m_widget.Toggle();
        m_logger.Log("Toggled the widget");
    } catch (...) {
        m_logger.Log("Exception while toggling the widget");
        throw;
    }

private:
    Widget& m_widget;
    Logger& m_logger;
};

void DoSomething(Widget& widget)
{
    Logger& logger = GetCurrentLogger();
    WidgetWrapper wrapper(widget, logger);

    // Do not use the widget directly!
    // Always use the wrapper!

    if (needs_toggling) {
        wrapper.Toggle();
    }
}

You want that “Do not use the widget directly!” comment to have some teeth. Can you “poison” the widget parameter so it cannot be used any more?

One idea is to split the method into two. The outer function does the preliminary wrapping, and the worker function does the bulk of the work.

void DoSomething(Widget& widget)
{
    Logger& logger = GetCurrentLogger();
    WidgetWrapper wrapper(widget, logger);

    DoSomethingWorker(wrapper, logger):
}

void DoSomethingWorker(
    WidgetRefWrapper& wrapper,
    Logger& logger)
{
    if (needs_toggling) {
        wrapper.Toggle();
    }
}

Since the raw widget is never passed to the worker function, there is no way to access it. If you type widget, you get an undefined identifier.

On the other hand, it also means that you have to pass all of the work you’ve done so far to the worker function. In this case, we pass the logger.

Also, somebody might see that you split the work into two functions and say, “What’s the point of a function that is called in only one place? I can just inline it!” and now you’re back where you started.

We can reuse the clever hack / shameless abuse of the language known as hide_name.

void DoSomething(Widget& widget)
{
    Logger& logger = GetCurrentLogger();
    WidgetWrapper wrapper(widget, logger);

    // From now on, all access must be done through the wrapper.
    hide_name widget;                                           

    if (needs_toggling) {
        wrapper.Toggle();
    }
}

Previously in “clever hack or shameless abuse of the language”: Bringing thread switching tasks to C#.

¹ Another case would be code that takes the inbound parameter and looks it up in some data structure to find a partner object, and we want all future operations to occur on the partner.

6 comments

Discussion is closed. Login to edit/delete existing comments.


Newest
Newest
Popular
Oldest
  • cricwood@nerdshack.com · Edited 0

    Nobody left a comment on starting a function with a try block instead of an opening curly bracket. So I guess everyone but me already knew that this is legal …?!

    Looks to me like a side effect of C++ permitting try-catch blocks in c’tor initializer lists.

    I can see those being useful in c’tors and d’tors, but is there a legitimate use case for normal functions? Aside from “impress your friends, confuse your enemies”?

  • Dmitry 0

    Of course, the latter can be ”fixed” even easier by someone who’s eager enough to use the forbidden. Easier than doing the inlining by hand.

  • Kevin Norris 0

    There is a part of me that is impressed. There is another part of me that thinks “This is just Greenspun’s tenth rule, but with Rust instead of Lisp.”

    • 紅樓鍮 0

      Haskell as well as many other strongly-typed FP languages have started to support linear types recently, so if you have a code idiom that’s sufficiently complex you may have to look there instead.

Feedback usabilla icon