c++ class - Quelqu'un a-t-il déjà utilisé la macro __COUNTER__ pré-processeur?




in include (12)

Je ne l'ai jamais utilisé pour autre chose qu'une macro DEBUG. C'est pratique de pouvoir dire

#define WAYPOINT \
    do { if(dbg) printf("At marker: %d\n", __COUNTER__); } while(0);

Le symbole __COUNTER__ est fourni par VC++ et GCC et donne une valeur intégrale non négative croissante à chaque utilisation.

Je suis intéressé de savoir si quelqu'un l'a déjà utilisé, et si c'est quelque chose qui mériterait d'être normalisé?


Il est utilisé dans le système de mesures ClickHouse.

namespace CurrentMetrics
{
    #define M(NAME) extern const Metric NAME = __COUNTER__;
        APPLY_FOR_METRICS(M)
    #undef M
    constexpr Metric END = __COUNTER__;

    std::atomic<Value> values[END] {};    /// Global variable, initialized by zeros.

    const char * getDescription(Metric event)
    {
        static const char * descriptions[] =
        {
        #define M(NAME) #NAME,
            APPLY_FOR_METRICS(M)
        #undef M
        };

        return descriptions[event];
    }

    Metric end() { return END; }
}


J'ai l'intention d'utiliser __COUNTER__ pour attribuer un identifiant unique à chaque fichier de notre base de code, de sorte que ce code unique puisse être utilisé pour la journalisation d'ASSERT dans un système intégré.

Cette méthode est beaucoup plus efficace que l’utilisation de chaînes pour stocker les noms de fichiers (avec __FILE__ ), en particulier sur un système intégré avec une mémoire ROM minuscule. Je pensais à l'idée en lisant cet article - Affirmez-vous sur Embedded.com. C'est dommage que cela ne fonctionne qu'avec les compilateurs basés sur GCC.


Je suis intéressé à savoir si quelqu'un l'a déjà utilisé,

Oui, mais comme vous pouvez le constater à partir de nombreux exemples dans ce Q & A, __LINE__ , qui est normalisé, serait également suffisant dans la plupart des cas.

__COUNTER__ n'est vraiment nécessaire que dans les cas où le nombre doit augmenter d'une fois ou qu'il doit avoir une continuité sur plusieurs fichiers #include .

et si c'est quelque chose qui mériterait d'être normalisé?

__COUNTER__ , contrairement à __LINE__ , est très dangereux car cela dépend des fichiers d’en-tête inclus et de leur ordre. Si deux fichiers .cpp (unités de traduction) incluent un fichier d’en-tête qui utilise __COUNTER__ , mais que le fichier d’en-tête obtient des séquences de comptage différentes dans les différentes instances, ils peuvent utiliser différentes définitions de la même chose et enfreindre la règle de définition unique.

Les violations de règles à définition unique sont très difficiles à détecter et peuvent créer des bogues et des risques pour la sécurité. Les quelques cas d' __COUNTER__ de __COUNTER__ ne l'emportent pas sur les inconvénients et le manque d'évolutivité.

Même si vous ne __COUNTER__ jamais de code utilisant __COUNTER__ , cela peut être utile lors du prototypage d'une séquence d'énumération, ce qui vous évite d'avoir à assigner des noms avant que l'appartenance ne soit concrète.


__COUNTER__ est utile partout où vous avez besoin d'un nom unique. Je l'ai largement utilisé pour les serrures et les piles de style RAII. Considérer:

struct TLock
{
  void Lock();
  void Unlock();
}
g_Lock1, g_Lock2;

struct TLockUse
{
  TLockUse( TLock &lock ):m_Lock(lock){ m_Lock.Lock(); }
  ~TLockUse(){ m_Lock.Unlock(); }

  TLock &m_Lock;
};

void DoSomething()
{
  TLockUse lock_use1( g_Lock1 );
  TLockUse lock_use2( g_Lock2 );
  // ...
}

Il est fastidieux de nommer les verrous utilisés et peut même devenir une source d’erreurs s’ils ne sont pas tous déclarés au sommet d’un bloc. Comment savoir si vous êtes sur lock_use4 ou lock_use11 ? C'est aussi une pollution inutile de l'espace de noms - je n'ai jamais besoin de faire référence aux objets d'utilisation des verrous par leur nom. Donc j'utilise __COUNTER__ :

#define CONCAT_IMPL( x, y ) x##y
#define MACRO_CONCAT( x, y ) CONCAT_IMPL( x, y )
#define USE_LOCK( lock ) TLockUse MACRO_CONCAT( LockUse, __COUNTER__ )( lock )

void DoSomething2()
{
  USE_LOCK( g_Lock1 );
  USE_LOCK( g_Lock2 );
  // ...
}

Mais ne vous attardez pas sur le fait que j'ai appelé les objets verrouillés - toute fonction qui doit être appelée par paires identiques correspond à ce modèle. Vous pouvez même avoir plusieurs utilisations sur le même "verrou" dans un bloc donné.


Si je comprends bien la fonctionnalité, j'aurais souhaité disposer de cette fonctionnalité lorsque je travaillais dans Perl, en ajoutant une fonction de journalisation des événements dans une interface graphique existante. Je voulais m'assurer que les tests manuels nécessaires (soupir) nous __counter__ une couverture complète. J'ai donc consigné chaque point de test dans un fichier, et la consignation d'une valeur __counter__ permettait de voir facilement ce qui manquait dans la couverture. En l'état, j'ai codé à la main l'équivalent.


Dans notre code, nous avons oublié d'ajouter des cas de test pour certains de nos produits. J'ai maintenant mis en place des macros afin de pouvoir affirmer au moment de la compilation que nous avons des cas de test pour chaque produit que nous ajoutons ou supprimons.


__COUNTER__ est unique, contrairement à __LINE__ . Certains compilateurs autorisent la __LINE__ de __LINE__ . Les fichiers #include réinitialiseront également __LINE__ .


Il est utilisé par Boost.Asio pour implémenter des routines sans pile.

Voir ce fichier d'en-tête et des examples .

Les coroutines résultantes ressemblent à ceci:

struct task : coroutine
{
  ...
  void operator()()
  {
    reenter (this)
    {
      while (... not finished ...)
      {
         ... do something ...
         yield;
         ... do some more ...
         yield;
       }
     }
   }
   ...
};

Une utilisation est dans la macro REGISTER_KERNEL_BUILDER de TensorFlow . Chaque implémentation de TensorFlow Op peut avoir un ou plusieurs noyaux. Ces noyaux sont enregistrés auprès d'un registraire. L’enregistrement d’un noyau se fait en définissant une variable globale - le constructeur de la variable peut effectuer l’enregistrement. Ici, les auteurs utilisent __COUNTER__ pour attribuer un nom unique à chaque variable globale.

#define REGISTER_KERNEL_BUILDER(kernel_builder, ...) \
  REGISTER_KERNEL_BUILDER_UNIQ_HELPER(__COUNTER__, kernel_builder, __VA_ARGS__)

#define REGISTER_KERNEL_BUILDER_UNIQ_HELPER(ctr, kernel_builder, ...) \
  REGISTER_KERNEL_BUILDER_UNIQ(ctr, kernel_builder, __VA_ARGS__)

#define REGISTER_KERNEL_BUILDER_UNIQ(ctr, kernel_builder, ...)          \
  static ::tensorflow::kernel_factory::OpKernelRegistrar                \
  registrar__body__##ctr##__object(                                 \
      SHOULD_REGISTER_OP_KERNEL(#__VA_ARGS__)                       \
      ? ::tensorflow::register_kernel::kernel_builder.Build()   \
      : nullptr,                                                \
      #__VA_ARGS__, [](::tensorflow::OpKernelConstruction* context) \
            -> ::tensorflow::OpKernel* {                \
              return new __VA_ARGS__(context);          \
            });

Emacs est un IDE.

edit : OK, je vais élaborer. Qu'est-ce qu'un IDE?

Comme point de départ, développons l’acronyme: Environnement de développement intégré. Pour analyser cela, je commence par la fin.

Un environnement est, en général, la partie du monde qui entoure le point de vue. Dans ce cas, il s’agit de ce que nous voyons sur notre moniteur (peut-être par nos haut-parleurs) et que nous manipulons via notre clavier (et peut-être une souris).

Le développement est ce que nous voulons faire dans cet environnement, son but, si vous voulez. Nous utilisons l'environnement pour développer des logiciels. Cela définit les sous-parties dont nous avons besoin: un éditeur, une interface vers le REPL, resp. le compilateur, une interface avec le débogueur et un accès à la documentation en ligne (cette liste peut ne pas être exhaustive).

Intégré signifie que toutes les parties de l'environnement sont en quelque sorte sous une surface uniforme. Dans un IDE, nous pouvons accéder aux différentes sous-parties et les utiliser avec un minimum de commutation; nous n'avons pas à quitter notre environnement défini. Cette intégration permet une meilleure interaction des différentes sous-parties. Par exemple, l’éditeur peut savoir dans quelle langue nous écrivons et nous indiquer l’auto-complétion des symboles, la définition directe, la mise en retrait automatique, la coloration syntaxique, etc. Il peut obtenir des informations du compilateur, passer automatiquement aux erreurs, et les mettre en évidence. Dans la plupart des IDE, sinon tous, l'éditeur est naturellement au cœur du processus de développement.

Emacs fait tout cela, il le fait avec un large éventail de langues et de tâches, et le fait avec excellence, car il est extensible de manière transparente par l'utilisateur chaque fois qu'il manque quelque chose.

Contre-exemple: vous pouvez développer en utilisant quelque chose comme Notepad, accéder à la documentation via Firefox et XPdf et diriger le compilateur et le débogueur à partir d'un shell. Ce serait un environnement de développement, mais il ne serait pas intégré .







c++ c