Non consente l'archiviazione non locale di un oggetto

2

Ho bisogno di scrivere una classe in C ++ che acquisisca una risorsa hardware e la rilasci quando viene distrutta. Fondamentalmente ciò può essere fatto nel costruttore e nel distruttore della classe. La difficoltà che sto avendo è che voglio che questa classe sia utilizzabile solo come oggetto locale.

Quindi in pratica:

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

Questo dovrebbe essere permesso:

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

Questo dovrebbe fallire:

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

Il mio ambiente è privo di eccezioni (incorporato e compilato con gcc -fno-exceptions), e se le cose vanno terribilmente storte di solito non c'è modo di gestirle tranne che correggere il bug che ha causato l'arresto.

Come posso farlo?

    
posta Christoph 10.10.2014 - 17:21
fonte

3 risposte

1

Non puoi abbastanza farlo, ma puoi avvicinarti:

#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
}
    
risposta data 11.10.2014 - 04:47
fonte
1

Non mi piace davvero rispondere alla mia domanda qui, soprattutto perché ho capito che ho fatto la domanda sbagliata. Ero troppo concentrato sui termini e sulle abitudini di OO, ma la realtà è che quello che volevo era un prodedure , non una classe o qualcosa che potesse effettivamente memorizzare uno stato. Ci scusiamo per questo.

Ecco la mia soluzione (non del tutto funzionante) (soluzione migliorata di seguito):

Ho ricordato che ci sono macro che gestiscono "blocchi atomici" cancellando un flag di abilitazione dell'interrupt quando viene inserito il blocco e ripristinandolo dopo che il blocco è stato eseguito. Ho dato un'occhiata a quelli e sono fondamentalmente solo un ciclo for .

Insieme a una definizione di struttura anonima nell'inizializzazione del ciclo for ( link ) questa soluzione è diventata una soluzione che funziona per 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()

Demo online: link

La macro for non può essere memorizzata, quindi non è non consentire l'archiviazione non locale , ma semplicemente fare qualcosa in un ordine specifico.

È meglio

La soluzione di cui sopra ha un grosso problema: release() non viene chiamato quando break o return viene chiamato nel ciclo, perché release() è nella parte "increment" e non in un distruttore. Sfortunatamente, un distruttore per l'anonimo non può essere definito. Così ho aggiunto una struttura interna con nome per farlo:

#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));
}

Demo online: link

    
risposta data 11.10.2014 - 11:07
fonte
0

Non penso che tu possa impedire la creazione di oggetti con durata di archiviazione statica (es. C ++ - Prevenire l'istanziazione globale? )

Potresti eseguire un controllo in fase di 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;
}

Il distruttore di variabili con durata di archiviazione statica

  • viene chiamato dopo che main() finisce e
  • per questo motivo stamperà un messaggio di errore

Ovviamente questo è solo un controllo, ma può essere piuttosto utile per correggere il codice.

Considera che non impedisce qualcosa del tipo:

int main()
{
   Magic useHardwareWith(settings);

   // ...
}
    
risposta data 11.10.2014 - 08:51
fonte

Leggi altre domande sui tag