关于c ++:有没有人曾经使用过__COUNTER__预处理器宏?

Has anyone ever had a use for the __COUNTER__ pre-processor macro?

__COUNTER__符号由vc++和gcc提供,每次使用时都会增加非负整数值。

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


__counter__在任何需要唯一名称的地方都很有用。我广泛用于RAII样式的锁和堆栈。考虑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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上?这也是对名称空间不必要的污染——我从不需要按名称引用lock-use对象。所以我用__counter__

1
2
3
4
5
6
7
8
9
10
#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 );
  // ...
}

但是不要因为我调用了对象锁这个事实而挂掉——任何需要在匹配的对中被调用的函数都符合这个模式。在给定的块中,您甚至可能在同一个"锁"上有多个用途。


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

  • 在C中构建时断言表达式的方法

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


除了调试宏,我从来没有用过它。说起来很方便

1
2
3
#define WAYPOINT \
    do { if(dbg) printf("At marker: %d

", __COUNTER__); } while(0);


它在Xcover代码覆盖率库中使用,用于标记执行通过的行,以查找未覆盖的行。


I'm interested to learn whether anyone's ever used it,

是的,但是从本问答中的许多例子中可以看出,标准化的__LINE__在大多数情况下也足够了。

只有在计数每次必须增加一个的情况下,或者它必须在多个#include文件上具有连续性时,才真正需要__counter__

and whether it's something that would be worth standardising?

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

一个定义规则的违反是很难捕捉的,并且可能造成错误和安全风险。__counter__的少数用例并没有真正超过缺点和缺乏可扩展性。

即使您从未发布使用__counter__的代码,它在构建枚举序列原型时也很有用,这样可以避免在成员身份具体化之前分配名称的麻烦。


如果我正确理解了这些功能,我希望在Perl中工作时拥有这些功能,在现有的GUI中添加一个事件日志记录函数。我想确保所需的手工测试(SIGH)给我们提供了完整的覆盖范围,所以我将每个测试点都记录到一个文件中,并记录一个__counter__值,这样可以很容易地看到覆盖范围中缺少的内容。事实上,我手工编码了等价物。


它被boost.asio用来实现无堆栈协程。

请参阅此头文件和示例。

结果协程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct task : coroutine
{
  ...
  void operator()()
  {
    reenter (this)
    {
      while (... not finished ...)
      {
         ... do something ...
         yield;
         ... do some more ...
         yield;
       }
     }
   }
   ...
};


我在本文中使用它来生成独特的类型:http://www.codeproject.com/articles/42021/sealing-classes-in-c


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


它在Clickhouse的度量系统中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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; }
}


TensorFlow的REGISTER_KERNEL_BUILDER宏中有一个用法。每个TensorFlow操作都可以有一个或多个内核作为其实现。这些内核在注册处注册。内核的注册是通过定义一个全局变量来完成的——变量的构造函数可以进行注册。在这里,作者使用__counter__给每个全局变量一个唯一的名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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);          \
            });


__LINE__不同,__counter__保证是唯一的。有些编译器允许重置__LINE__。#include文件还将重置__LINE__


我打算使用__counter__为代码库中的每个文件提供一个唯一的标识符,以便在嵌入式系统中记录断言时使用唯一的代码。

这个方法比使用字符串来存储文件名(使用__FILE__)要有效得多,尤其是在一个带有微型只读存储器的嵌入式系统上。我在阅读本文时想到了这个想法——在embedded.com上断言自己。但是,它只与基于GCC的编译器一起工作是一件很遗憾的事情。