【问题标题】:Has anyone ever had a use for the __COUNTER__ pre-processor macro?有没有人使用过 __COUNTER__ 预处理器宏?
【发布时间】:2010-10-13 18:16:04
【问题描述】:

__COUNTER__ 符号由VC++ 和 GCC 提供,每次使用时都会给出一个递增的非负整数值。

我有兴趣了解是否有人使用过它,以及它是否值得标准化?

【问题讨论】:

  • 好奇:COUNTER 是在 C99 标准中定义的吗? (或者可能是 C++ 标准?)
  • 我认为两者都没有定义。 AFAIK,它是一个从 VC++ 开始的扩展,最近转到了 GCC。不知道有没有其他编译器支持。也许如果它有真正重要的用途,那就有理由标准化......
  • 我用过,但是太久远了,我不记得为什么了。
  • @strager: COUNTER_COUNTER_ 都没有出现在 C (99) 标准中。
  • @dcw:clang 支持它。

标签: c++ c


【解决方案1】:

除了 DEBUG 宏之外,我从未将它用于其他任何用途。可以说很方便

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

【讨论】:

  • 有趣。但这与使用 FILE 一样好/更好。毕竟,这会告诉你航点的确切位置,而不是可能不得不在文件中搜索 WAYPOINT 实例?我错过了什么吗;-)
  • 不,没有。它是故意的。如果有人写WAYPOINT;额外的;是无操作的,静默优化。反之,它可能会成为宏以南某处的语法错误,原因隐藏在宏体中。
  • 第二个分号将使它失败,例如if (foo) WAYPOINT; else bar();。您正在重新介绍do { foo } while (0) 解决的问题:)
  • @CharlieMartin 宏中do { foo } while (0) 的全部意义在于避免if/else 语句的问题。如果您不担心没有大括号的 if 语句(这很公平),为什么不删除整个 while (0) 包装器呢?无论哪种方式,while (0) 包装器在结尾处的分号完全没有意义。
  • 在这种情况下,您确实需要删除结尾的分号!这就是我所说的“无论哪种方式......”。
【解决方案2】:

我在编译时断言宏中使用它来让宏为 typedef 创建一个唯一的名称。见

如果你想要血淋淋的细节。

【讨论】:

  • 为什么要为每个 typedef 提供唯一的名称?它们要么被等价定义,要么编译失败。
  • @nOrd - 嗯 - 要么是过度设计,要么是因为某种原因让我无法理解......但是,我可能不会重新审视它,因为 C_ASSERT 的东西似乎对我来说很好用.
  • 我相信,至少在 C89 中,等效定义是一个错误。我认为 C99 和 C++11 都允许冗余类型定义,但它们并不总是被允许的。
  • 如果您多次调用宏,您需要每个 typedef 都是唯一的。如果__COUNTER__ 不可用,则故障转移是使用__LINE__,但这会失败,因为你运气不好在两个不同的源文件的同一行上使用宏。
【解决方案3】:

如果我正确理解了该功能,我希望我在使用 Perl 时拥有该功能,将事件日志功能添加到现有 GUI 中。我想确保所需的手动测试(叹气)为我们提供了完整的覆盖范围,因此我将每个测试点记录到一个文件中,并记录 __counter__ 值可以轻松查看覆盖范围中缺少的内容。实际上,我手动编写了等效代码。

【讨论】:

  • 当我重读自己的帖子时,我并没有真正手动编写等效代码。我只是用手把计数器放进去。 (硬编码每个文件中的所有数字。)这就是为什么我希望我有这个功能。
【解决方案4】:

xCover代码覆盖库中使用,用于标记执行经过的行,查找未被覆盖的行。

【讨论】:

    【解决方案5】:

    __COUNTER__ 在您需要唯一名称的任何地方都很有用。我已将它广泛用于 RAII 风格的锁和堆栈。考虑:

    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 );
      // ...
    }
    

    为锁的用途命名会很乏味,如果它们没有全部声明在块的顶部,甚至会成为错误的来源。你怎么知道你是在lock_use4 还是lock_use11?这也是对命名空间的不必要污染——我永远不需要按名称引用锁使用对象。所以我用__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 );
      // ...
    }
    

    但是不要纠结于我将对象称为锁这一事实——任何需要成对调用的函数都适合这种模式。您甚至可能对给定块中的同一个“锁”有多种用途。

    【讨论】:

    • 使用一个足够智能的use_lock(g_Lock1, g_Lock2) 函数来订购锁以避免死锁是一个明显更好的主意。
    • 为什么需要 MACRO_CONCAT 和 CONCAT_IMPL?不能直接写LockUse##__COUNTER__吗?
    • @Cœur - 仅供参考,几年前我在这个网站上问过同样的问题:Why is a level of indirection needed for this concatenation macro?
    【解决方案6】:

    我打算使用__COUNTER__ 为我们代码库中的每个文件提供一个唯一标识符,以便可以使用该唯一代码在嵌入式系统中记录 ASSERT。

    这种方法比使用字符串存储文件名(使用__FILE__)要高效得多,尤其是在具有微型ROM 的嵌入式系统上。我在阅读这篇文章时想到了这个想法 - Embedded.com 上的Assert Yourself。可惜它只适用于基于 GCC 的编译器。

    【讨论】:

    • __COUNTER__ 会为每个 cpp 文件重置,因此标题将根据包含的顺序获得不同的编号。我想你记错了。
    【解决方案7】:

    我在本文中使用它来生成唯一类型: http://www.codeproject.com/Articles/42021/Sealing-Classes-in-C

    【讨论】:

      【解决方案8】:

      __COUNTER____LINE__ 不同,保证是唯一的。一些编译器允许__LINE__ 被重置。 #include 文件也会重置__LINE__

      【讨论】:

      • 所有标准 C 和 C++ 编译器都必须提供重置 __LINE__ 的功能。
      【解决方案9】:

      我很想知道是否有人用过它,

      是的,但正如您从本问答中的许多示例中看​​到的那样,标准化的__LINE__ 在大多数情况下也足够了。

      __COUNTER__ 仅在计数必须每次增加 1 或必须在多个 #include 文件上具有连续性的情况下才真正需要。

      以及它是否值得标准化?

      __COUNTER____LINE__ 不同,它非常危险,因为它取决于包含哪些头文件以及什么顺序。如果两个.cpp文件(翻译单元)包含一个使用__COUNTER__的头文件,但头文件在不同的实例中得到不同的计数序列,则可能对同一事物使用不同的定义,违反了单定义规则。

      单一定义的违反规则很难被发现,并且可能会产生错误和安全风险。 __COUNTER__ 的少数用例并没有真正超过缺点和缺乏可扩展性。

      即使您从未发布使用 __COUNTER__ 的代码,它在对枚举序列进行原型设计时也很有用,从而避免了在成员资格具体化之前指定名称的麻烦。

      【讨论】:

        【解决方案10】:

        Boost.Asio 使用它来实现无堆栈协程。

        查看header fileexamples

        生成的协程如下所示:

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

        【讨论】:

          【解决方案11】:

          用法在TensorFlow's REGISTER_KERNEL_BUILDER macro。每个 TensorFlow Op 可以有一个或多个内核作为其实现。这些内核在注册商处注册。内核的注册是通过定义一个全局变量来完成的——变量的构造函数可以进行注册。在这里,作者使用__COUNTER__ 为每个全局变量赋予一个唯一的名称。

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

          【讨论】:

            【解决方案12】:

            在 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; }
            }
            

            【讨论】:

              【解决方案13】:

              在我们的代码中,我们忘记为我们的某些产品添加测试用例。我现在实现了一些宏,这样我们就可以在编译时断言我们对每个要添加或删除的产品都有测试用例。

              【讨论】:

                【解决方案14】:

                我已将它用于驱动程序 shim 层,我需要确保至少启用了一个物理驱动程序。

                例如:

                #if defined( USE_DRIVER1 )
                #include "driver1.h"
                int xxx1 = __COUNTER__;
                #endif
                #if defined( USE_DRIVER2 )
                #include "driver2.h"
                int xxx2 = __COUNTER__;
                #endif
                #if __COUNTER__ < 1
                #error Must enable at least one driver.
                #endif
                

                【讨论】:

                  【解决方案15】:

                  在这个blog post中用来模拟C++11中golang的defer语句。

                  template <typename F>
                  struct privDefer {
                      F f;
                      privDefer(F f) : f(f) {}
                      ~privDefer() { f(); }
                  };
                  
                  template <typename F>
                  privDefer<F> defer_func(F f) {
                      return privDefer<F>(f);
                  }
                  
                  #define DEFER_1(x, y) x##y
                  #define DEFER_2(x, y) DEFER_1(x, y)
                  #define DEFER_3(x)    DEFER_2(x, __COUNTER__)
                  #define defer(code)   auto DEFER_3(_defer_) = defer_func([&](){code;})
                  

                  那么你可以这样做:

                  int main()
                  {
                      FILE* file = open("file.txt");
                      defer(fclose(file));
                  
                      // use the file here
                      // ....
                  }
                  

                  【讨论】:

                  • 使用 RAII 习语可以更好地处理这个问题。我知道您引用的博客说编写一次性类很不方便,但std::unique_ptr 使用自定义删除器解决了这个问题。
                  • @MattEding 好吧,这在某种程度上已经在使用 RAII 成语了。博客并没有说这很不方便。
                  • 没错,它在实现中使用了 RAII,但之后的手动调用似乎违背了它的精神。该博客暗示了以下不便:“您可能会说 C++ 中的 RAII 解决了这个问题,但这需要一个类包装器。defer 允许与 RAII 相同的东西,但无需手动创建包装器类型。”
                  • @MattEding 我没有看到这句话的含义,也许你只是自己看到了不便:)
                  【解决方案16】:

                  __COUNTER__ 非常有用,当您在运行时加密字符串并且您希望每个字符串都有一个唯一的密钥,而不是在某处为您的加密密钥存储计数器时,您可以使用 Counter 来确保每个字符串都有它拥有独一无二的钥匙!

                  我在我的 XorString 1 头库中使用它,它在运行时解密字符串,所以如果任何黑客/破解者试图查看我的二进制文件,他们不会在那里找到字符串,但是当程序运行时字符串被解密并正常显示。

                  #pragma once
                  #include <string>
                  #include <array>
                  #include <cstdarg>
                  
                  #define BEGIN_NAMESPACE( x ) namespace x {
                  #define END_NAMESPACE }
                  
                  BEGIN_NAMESPACE(XorCompileTime)
                  
                  constexpr auto time = __TIME__;
                  constexpr auto seed = static_cast< int >(time[7]) + static_cast< int >(time[6]) * 10 + static_cast< int >(time[4]) * 60 + static_cast< int >(time[3]) * 600 + static_cast< int >(time[1]) * 3600 + static_cast< int >(time[0]) * 36000;
                  
                  // 1988, Stephen Park and Keith Miller
                  // "Random Number Generators: Good Ones Are Hard To Find", considered as "minimal standard"
                  // Park-Miller 31 bit pseudo-random number generator, implemented with G. Carta's optimisation:
                  // with 32-bit math and without division
                  
                  template < int N >
                  struct RandomGenerator
                  {
                  private:
                      static constexpr unsigned a = 16807; // 7^5
                      static constexpr unsigned m = 2147483647; // 2^31 - 1
                  
                      static constexpr unsigned s = RandomGenerator< N - 1 >::value;
                      static constexpr unsigned lo = a * (s & 0xFFFF); // Multiply lower 16 bits by 16807
                      static constexpr unsigned hi = a * (s >> 16); // Multiply higher 16 bits by 16807
                      static constexpr unsigned lo2 = lo + ((hi & 0x7FFF) << 16); // Combine lower 15 bits of hi with lo's upper bits
                      static constexpr unsigned hi2 = hi >> 15; // Discard lower 15 bits of hi
                      static constexpr unsigned lo3 = lo2 + hi;
                  
                  public:
                      static constexpr unsigned max = m;
                      static constexpr unsigned value = lo3 > m ? lo3 - m : lo3;
                  };
                  
                  template <>
                  struct RandomGenerator< 0 >
                  {
                      static constexpr unsigned value = seed;
                  };
                  
                  template < int N, int M >
                  struct RandomInt
                  {
                      static constexpr auto value = RandomGenerator< N + 1 >::value % M;
                  };
                  
                  template < int N >
                  struct RandomChar
                  {
                      static const char value = static_cast< char >(1 + RandomInt< N, 0x7F - 1 >::value);
                  };
                  
                  template < size_t N, int K, typename Char >
                  struct XorString
                  {
                  private:
                      const char _key;
                      std::array< Char, N + 1 > _encrypted;
                  
                      constexpr Char enc(Char c) const
                      {
                          return c ^ _key;
                      }
                  
                      Char dec(Char c) const
                      {
                          return c ^ _key;
                      }
                  
                  public:
                      template < size_t... Is >
                      constexpr __forceinline XorString(const Char* str, std::index_sequence< Is... >) : _key(RandomChar< K >::value), _encrypted{ enc(str[Is])... }
                      {
                      }
                  
                      __forceinline decltype(auto) decrypt(void)
                      {
                          for (size_t i = 0; i < N; ++i) {
                              _encrypted[i] = dec(_encrypted[i]);
                          }
                          _encrypted[N] = '\0';
                          return _encrypted.data();
                      }
                  };
                  
                  //--------------------------------------------------------------------------------
                  //-- Note: XorStr will __NOT__ work directly with functions like printf.
                  //         To work with them you need a wrapper function that takes a const char*
                  //         as parameter and passes it to printf and alike.
                  //
                  //         The Microsoft Compiler/Linker is not working correctly with variadic 
                  //         templates!
                  //  
                  //         Use the functions below or use std::cout (and similar)!
                  //--------------------------------------------------------------------------------
                  
                  static auto w_printf = [](const char* fmt, ...) {
                      va_list args;
                      va_start(args, fmt);
                      vprintf_s(fmt, args);
                      va_end(args);
                  };
                  
                  static auto w_printf_s = [](const char* fmt, ...) {
                      va_list args;
                      va_start(args, fmt);
                      vprintf_s(fmt, args);
                      va_end(args);
                  };
                  
                  static auto w_sprintf = [](char* buf, const char* fmt, ...) {
                      va_list args;
                      va_start(args, fmt);
                      vsprintf(buf, fmt, args);
                      va_end(args);
                  };
                  
                  static auto w_sprintf_ret = [](char* buf, const char* fmt, ...) {
                      int ret;
                      va_list args;
                      va_start(args, fmt);
                      ret = vsprintf(buf, fmt, args);
                      va_end(args);
                      return ret;
                  };
                  
                  static auto w_sprintf_s = [](char* buf, size_t buf_size, const char* fmt, ...) {
                      va_list args;
                      va_start(args, fmt);
                      vsprintf_s(buf, buf_size, fmt, args);
                      va_end(args);
                  };
                  
                  static auto w_sprintf_s_ret = [](char* buf, size_t buf_size, const char* fmt, ...) {
                      int ret;
                      va_list args;
                      va_start(args, fmt);
                      ret = vsprintf_s(buf, buf_size, fmt, args);
                      va_end(args);
                      return ret;
                  };
                  
                  //Old functions before I found out about wrapper functions.
                  //#define XorStr( s ) ( XorCompileTime::XorString< sizeof(s)/sizeof(char) - 1, __COUNTER__, char >( s, std::make_index_sequence< sizeof(s)/sizeof(char) - 1>() ).decrypt() )
                  //#define XorStrW( s ) ( XorCompileTime::XorString< sizeof(s)/sizeof(wchar_t) - 1, __COUNTER__, wchar_t >( s, std::make_index_sequence< sizeof(s)/sizeof(wchar_t) - 1>() ).decrypt() )
                  
                  //Wrapper functions to work in all functions below
                  #define XorStr( s ) []{ constexpr XorCompileTime::XorString< sizeof(s)/sizeof(char) - 1, __COUNTER__, char > expr( s, std::make_index_sequence< sizeof(s)/sizeof(char) - 1>() ); return expr; }().decrypt()
                  #define XorStrW( s ) []{ constexpr XorCompileTime::XorString< sizeof(s)/sizeof(wchar_t) - 1, __COUNTER__, wchar_t > expr( s, std::make_index_sequence< sizeof(s)/sizeof(wchar_t) - 1>() ); return expr; }().decrypt()
                  
                  END_NAMESPACE
                  

                  【讨论】:

                  • 这不是你应该做的事情。
                  • 为什么不呢?似乎是使用它的完美理由。
                  • 与所有 DRM 方案一样,这使您的合法用户的生活更加困难,而不是阻止您想要阻止的任何事情。
                  【解决方案17】:

                  生成类类型 ID (C++)

                  我已经使用 __COUNTER__ 在面向对象的游戏中为实体和碰撞器自动生成类型 ID。

                  这个游戏使用多态来实现它的功能。要序列化子对象,我必须想出一种方法来存储实体子类型并序列化/反序列化它们以进行场景保存和加载。从保存文件中读取实体(反序列化)时,我需要知道要读取哪些属性;使用__COUNTER__,我为每个实体类都有一个唯一且恒定的 ID,并且可以使用此 ID 将它们作为正确的实体类型加载。

                  这种方法意味着要使新的 Entity 类型可序列化,我只需在构造函数中添加 typeID = __COUNTER__; 即可覆盖默认 ID。以雪碧为例:

                  Sprite(/* TODO: Sprite Arguments */) : Entity(/* TODO: Entity Arguments */) {
                      typeID = __COUNTER__;
                  }
                  

                  ...并继续概述其 iostream 重载:

                  friend std::ostream& operator<<(std::ostream& os, const Sprite& rhs) {
                      return os << /* TODO: Outline Output */;
                  }
                  friend std::istream& operator>>(std::istream& is, Sprite& rhs) {
                      return is >> /* TODO: Outline Input */;
                  }
                  

                  这是为您的类生成类型 ID 的一种非常轻量级的方法,并且避免了一堆复杂的逻辑。作为预处理器命令,它非常基本,但它为一些关键设备提供了有用的工具。

                  注意:如果您想在调用计数器时将 ID 值重新设置为 0,请将其值存储在第一个 ID 的生成中,然后将所有后续 ID 减去该值。

                  感谢阅读! -YZM

                  【讨论】:

                    【解决方案18】:

                    我发现它对于在 UI 中显示步骤很有用。这使得添加、删除或重新排序步骤变得非常容易,而不必担心步骤被贴错标签。

                    #include <iostream>
                    
                    #define STR_IMPL(s)  #s
                    #define STR(s)  STR_IMPL(s)
                    #define STEP  STR(__COUNTER__) ": "
                    
                    int main()
                    {
                        std::cout 
                            << STEP "foo\n"
                            << STEP "bar\n"
                            << STEP "qux\n"
                            ;
                    }
                    

                    输出:

                    0: foo
                    1: bar
                    2: qux
                    

                    让它从 1 开始而不是 0 留作练习。

                    【讨论】:

                      猜你喜欢
                      • 2011-06-10
                      • 1970-01-01
                      • 2011-10-20
                      • 1970-01-01
                      • 1970-01-01
                      • 2016-09-02
                      • 2014-10-01
                      • 2021-12-29
                      • 1970-01-01
                      相关资源
                      最近更新 更多