Nascondere i puntatori grezzi dall'API pubblica, ma è necessario passarli internamente - revisione del progetto

2

Sto progettando e prototipando un wrapper C ++ ncurses. È un progetto per hobby, niente di troppo serio. Tralascio le protezioni incluse e stdlib include ecc.

Considera i seguenti file:

application.hpp

#include "ncurses_forward_decl.hpp"
#include "window/window.hpp"

#define PPCURSES_VERSION_MAJOR 0
#define PPCURSES_VERSION_MINOR 3
#define PPCURSES_VERSION_PATCH 0

namespace ppc {

enum Focus { Canvas = -1 };

using WindowPtr = std::shared_ptr<Window>;

class Application {
 public:
  Application();
  ~Application();

  WindowPtr NewWindow(Point pos, Point size);

  // Event GetWindowEvents();
  char GetKeyEvents();
  void Draw();

 private:
  int focus_id_;
  std::unique_ptr<WINDOW> canvas_;
  std::vector<std::shared_ptr<Window>> windows_;
};

}  // namespace ppc

window.hpp

#include "ncurses_forward_decl.hpp"
#include "util/point.hpp"
#include "widgets/widget.hpp"

namespace ppc {
// FIXME: hide this deleter from public API
void RawWindowDeleter(WINDOW *win);

using WidgetPtr = std::shared_ptr<Widget>;

class Window {
  friend class Application;
  friend class Widget;

 public:
  Window(Point pos, Point size);

  template <class T, class... Args>
  void AddWidget(Args... args) {
    WidgetPtr widget = std::make_shared<T>(args...);
    widgets_.push_back(widget);
  }

  void SetPosition(Point t_position);
  inline Point Position() { return position_; }

  void SetSize(Point t_size);
  inline Point Size() { return size_; }

 protected:
  Point position_;
  Point size_;
  std::shared_ptr<WINDOW> curses_window_;

  inline WINDOW *RawPtr() { return curses_window_.get(); }
  void Draw();
  char GetKeyEvents();

 private:
  std::vector<WidgetPtr> widgets_;
};

}  // namespace ppc

widget.hpp

#include "ncurses_forward_decl.hpp"

namespace ppc {
class Window;

class Widget {
  friend class Window;

 public:
  virtual ~Widget() = default;

 protected:
  WINDOW *RawPtr(Window &win);
  virtual void Draw(Window &win) = 0;
};

}  // namespace ppc

label.hpp

#include "util/point.hpp"
#include "widget.hpp"

namespace ppc {

class Label : public Widget {
  friend class Window;

 public:
  Label(Point pos, const char *text);

  Point position;
  std::string text;

 protected:
  void Draw(Window &win) override;
};

}  // namespace ppc

L'applicazione può creare Windows, che a sua volta può creare e gestire widget (come un'etichetta). Ma, poiché i Widget devono essere in grado di Draw() , hanno bisogno del puntatore raw WINDOW * di ncurses, ottenuto tramite il metodo RawPtr() , ma sto cercando di nasconderlo dall'API pubblica. Il metodo RawPtr(Window &win) del widget restituisce il puntatore raw della finestra, poiché Widget è dichiarato amico di Window. Sto anche dichiarando Window come amico di Label e Application come amico di Window. Ritengo che questa sia una decisione sbagliata che porterà agli spaghetti in un secondo momento, e sarebbe molto più facile esporre RawPtr() in Window al pubblico. Ma davvero non voglio che gli utenti finali abbiano nulla a che fare con il funzionamento interno di ncurses. C'è un disegno che mi manca qui?

    
posta makos 10.09.2018 - 09:40
fonte

2 risposte

1

Credo che la risposta migliore sia rendere rawptr accessibile pubblicamente (ma importi protetti alla stessa cosa dato che è possibile creare una sottoclasse per accedere). E questo è un buon nome.

Basta documentare chiaramente per cercare di evitare l'uso del metodo. Documenta il suo basso livello e le eventuali regole che la tua implementazione impone al suo utilizzo.

Questo è il miglior compromesso.

Permette facilmente le estensioni. Ed evita la lunga lista di amici non modulari interconnessi di cui avresti bisogno per mantenerla privata.

Non vedo perché l'etichetta abbia bisogno della finestra della classe di amici.

Il metodo Rawptr del widget dovrebbe essere statico.

Non chiaro cosa fa rawwindowdeleter.

Non è chiaro il motivo per cui ha un'applicazione per la classe di amici.

    
risposta data 10.09.2018 - 17:08
fonte
0

A seconda delle tue esigenze, e una delle cose che mi hanno risposto in questo modo è che ti riferivi a "utenti" come se non fossero nella tua squadra interna, tendo ad andare con un'API non sicura con la sicurezza imposta sulla parte superiore staticamente.

Tende ad essere molto più semplice per esportare le funzioni più semplici ma forse meno sicure per una lib di dylib / condivisa perché queste sono tutte, forse sfortunatamente, intorno ai costrutti di basso livello di C e tutti i dettagli cruenti di ABI come chiamare convenzioni e tali senza alcuna consapevolezza intrinseca di cose come sovraccarico di funzioni o distruttori. Se vuoi massimizzare la gamma di compilatori che gli utenti possono utilizzare contro la tua API senza problemi ABI, allora tende ad essere un po 'più simile a C nella "raw API".

Quindi, se si desidera imporre la sicurezza, è possibile fornire una libreria statica, ad esempio, che avvolge quelle interfacce C o C meno sicure in alcuni set di wrapper C ++ conformi a RAII (internamente collegati ai binari dei tuoi utenti che utilizzano il tuo SDK / API). Questo è un dolore ed è un po 'come saltare gli ostacoli per recuperare le interfacce che inizialmente volevi, ma farlo in "due strati" come questo spesso massimizza la compatibilità con la più ampia gamma di compilatori (e FFI se questo è un obiettivo) e Riduci al minimo i tipi di problemi che potresti incontrare nella pratica di lavorare oltre i limiti del modulo.

In questi giorni sto saltellando avanti e indietro tra C e C ++ poiché uso C nelle intestazioni per l'SDK centrale / API (nel mio caso anche la compatibilità con FFI è una preoccupazione perché gli utenti tendono a usare queste API da linguaggi come Java , C #, lo scripting interno lo utilizza da Lua, ecc.) E poi C ++ per semplificare l'implementazione di tali API. E a causa della frequenza con cui eseguo questo tipo di interoperabilità, ho anche adottato la convenzione di distinguere .h per i file di intestazione dell'API C e .hpp per le intestazioni C ++.

Questo potrebbe essere eccessivo se si sta prendendo di mira alcune API per essere utilizzate da persone che usano gli stessi compilatori e così via. L'ho appena proposto perché il modo in cui descrivevi gli "utenti" qui e facendo cose come evitare la libreria standard mi faceva sospettare che tu volessi raggiungere il pubblico più vasto possibile, nel qual caso tendi a fare meglio astenendoti da costrutti oggetto come costruttori e distruttori e persino sovraccaricare le funzioni (problemi di mangling del nome / simbolo) ed evitare di lanciare eccezioni tra i confini del modulo e persino astenersi da vtables e così via per le funzioni "raw" esportate. Assente una libreria stub si può immaginare che anche costruttori e distruttori (indispensabili per il funzionamento dei puntatori intelligenti) debbano essere richiamati manualmente se i simboli vengono cercati in fase di esecuzione (es: utilizzando dlsym su Linux) e utilizzati in questo modo. Ma per tutto ciò che ha un collegamento interno con gli utenti della tua libreria, puoi fare di tutto con gli articoli in C ++.

    
risposta data 09.12.2018 - 17:12
fonte

Leggi altre domande sui tag