2

I need to write a class in C++ that acquires a hardware resource and releases it again when it is destroyed. Basically that can be done in the constructor and destructor of the class. The difficulty I'm having is that I want this class to be usable as a local object only.

So basically:

struct Magic
{
  Magic(const Settings& settings)
  {
    acquireHardware(settings);
  }
  ~Magic()
  {
    releaseHardware();
  }
  // Magic does not need to store the settings or a reference to them
};

This should be allowed:

void useAllTheHardware(const Settings& settings)
{
  Magic useHardwareWith(settings);
  ...
  // destructor releases the hardware
}

This should fail:

struct badIdea
{
  badIdea(const Settings& settings)
    : settings_(settings),
    useHardwareWith_(settings)
  {
  }
  Settings settings_;
  Magic useHardwareWith_;
};
badIdea mustFail(settings); // <-- not allowed

My environment is exception-free (embedded, and compiled with gcc -fno-exceptions), and if things go terribly wrong there's usually no way of handling that except fixing the bug that caused the crash.

How can I do that?

6
  • I'm curious what the motivation for only allowing one "local" instance is. Are you trying to prevent multiple references to the underlying hardware? – Gort the Robot Oct 11 '14 at 3:37
  • What counts as "local"? If I create an object in main in the outermost scope, it's no different from having a static variable ... – Rufflewind Oct 11 '14 at 4:28
  • @StevenBurnap I'm trying to prevent overlapped acquire() and release() pairs. The hardware driver used is quite simple and provides those methods as a means of synchronizing higher-level access, especially when those higher-level drivers use an interrupt service routine to acquire(). – Christoph Oct 11 '14 at 9:18
  • @Rufflewind indeed, that's correct. As you can see in my answer below, my whole view of my task ("create a class") was misleading my attempt at trying to come up with a solution. – Christoph Oct 11 '14 at 9:19
  • 1
    It sounds like what you really want is a singleton – Gort the Robot Oct 11 '14 at 15:36
1

You can't quite do it, but you can come close:

#include <cstddef>

#include <new>

struct Magic
{
    enum promise
    {
        i_promise_not_to_put_this_in_another_class
    };

    void *operator new(size_t) = delete;
    void *operator new[](size_t) = delete;
    void operator delete(void *) = delete;
    void operator delete[](void *) = delete;

    Magic(promise) {}
    Magic(Magic&&) = delete;
    Magic(const Magic&) = delete;
    Magic& operator = (Magic&&) = delete;
    Magic& operator = (const Magic&) = delete;
    ~Magic() {}
};

struct WhyIsThisABadIdea
{
    Magic m;

    WhyIsThisABadIdea() : m(Magic::i_promise_not_to_put_this_in_another_class) {} // obvious lies
};

int main()
{
    Magic m(Magic::i_promise_not_to_put_this_in_another_class); // okay
    //new Magic(Magic::i_promise_not_to_put_this_in_another_class); // error
    WhyIsThisABadIdea bad; // okay
}
1
  • I've thought about deleting a number of operators, just as you suggested, but the BadIdea kind of structs can always get in the way, just like you demonstrated. – Christoph Oct 11 '14 at 8:17
1

I really don't like answering my own question here, especially because I realized that I asked the wrong question. I was too much focused on OO terms and habits, but the reality is that what I wanted was a prodedure, not a class or anything that can actually store a state. Sorry for that.

Here's my (not quite working) solution (improved solution below):

I remembered that there are macros that manage "atomic blocks" by clearing an interrupt enable flag when the block is entered, and restoring it after the block was run. I had a look at those and they are basically just a for loop.

Together with an anonymous struct definition in the for loop initialization (https://stackoverflow.com/a/11255852) this grew to a solution that works for me:

#include <iostream>

struct Settings
{
    Settings(int v_) : v(v_) {}
    int v;
};

//https://stackoverflow.com/a/11255852
#define useHardwareWith(settings)                   \
acquireHardware(settings);                          \
for(                                                \
  struct                                            \
  {                                                 \
    bool done()                                     \
    {                                               \
      return done_;                                 \
    }                                               \
    bool run()                                      \
    {                                               \
      releaseHardware();                            \
      done_ = true;                                 \
    }                                               \
    bool done_;                                     \
  } magic = {false}; !magic.done() ; magic.run())

void acquireHardware(const Settings& settings)
{
  std::cout << "acquire(" << settings.v << ")\n";
}

void releaseHardware()
{
  std::cout << "release()\n";
}

int main()
{
  Settings settings(1);
  useHardwareWith(settings)
  {
    std::cout << "Doing important hardware stuff\n";
  }
}

Output:

acquire(1)
Doing important hardware stuff
release()

Online demo: http://ideone.com/eT91hD

The for macro cannot be stored, so it's not disallowing non-local storage, but simply doing something in a specific order.


This is better

Above solution has a major problem: release() is not called when break or return is called in the loop, because release() is in the "increment" part, and not in a destructor. Unfortunately, a destructor for the anonymous cannot be defined. So I added a named inner struct to do that:

#include <iostream>

struct Settings
{
    Settings(int v_) : v(v_) {}
    int v;
};

//https://stackoverflow.com/a/11255852
#define useHardwareWith(settings)                   \
acquireHardware(settings);                          \
for(                                                \
  struct                                            \
  {                                                 \
    struct inner                                    \
    {                                               \
      inner() : done_(false)                        \
      {                                             \
      }                                             \
      ~inner()                                      \
      {                                             \
        releaseHardware();                          \
      }                                             \
      bool done_;                                   \
    };                                              \
    bool done() const                               \
    {                                               \
      return inner_.done_;                          \
    }                                               \
    bool run()                                      \
    {                                               \
      inner_.done_ = true;                          \
    }                                               \
    inner inner_;                                   \
  } magic; !magic.done() ; magic.run())

void acquireHardware(const Settings& settings)
{
  std::cout << "acquire(" << settings.v << ")\n";
}

void releaseHardware()
{
  std::cout << "release()\n";
}

void doHardwareStuff(const Settings& settings)
{
  useHardwareWith(settings)
  {
    if (settings.v == 0)
    {
        std::cout << "side-exit\n";
        return;
    }
    std::cout << "Doing important hardware stuff\n";
  }
}

int main()
{
  Settings settings(0);
  doHardwareStuff(settings);
  doHardwareStuff(Settings(1));
}

Online demo: http://ideone.com/z3Qm8a

0

I don't think you can prevent the creation of objects with static storage duration (e.g. C++ - Prevent global instantiation?)

You could run a check at runtime:

bool ALLOW = false;

struct Magic
{
  // ...

  ~Magic()
  {
    if (!ALLOW)
      std::cerr << "Some diagnostic message\n";
    releaseHardware();
  }
}; 


int main()
{
  ALLOW = true;

  // ...

  ALLOW = false;  // reset before main() ends
  return 0;
}

The destructor of variables with static storage duration

  • is called after main() ends and
  • for this reason it will print an error message

Of course this is only a check, but can be quite useful to correct the code.

Consider that it doesn't prevent something like:

int main()
{
   Magic useHardwareWith(settings);

   // ...
}
2
  • It is possible to create "only local" objects by using an anonymous struct, see stackoverflow.com/a/11255852 or my selfie-answer. Having a global guard variable (Allow) is also not very handy if multiple different resources must be managed. – Christoph Oct 11 '14 at 9:24
  • The ALLOW variable won't prevent the creation of multiple Magic objects (it isn't a guard): it's used just to signal if you have created such objects. Your solution is nice and, of course, is a solution ;-) but it seems to me that it doesn't regard the only-local constraint. – manlio Oct 11 '14 at 9:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.