是否可以在不增加包含对象大小的情况下添加私有成员变量?

Is it possible to add a private member-variable without increasing the containing object's size?

我有一个名为 ObjectCounter 的小工具 class,它没有虚方法,也没有成员变量;它只包含一个构造函数和一个析构函数,它们分别递增和递减一个全局变量:

int _objectCount = 0;  // global

class ObjectCounter
{
public:
   ObjectCounter() {printf("DefaultCtor:  count=%i\n", ++_objectCount);}
   ~ObjectCounter() {printf("Dtor:  count=%i\n", --_objectCount);}
};

当我想跟踪我的程序在任何给定时间创建的另一个 class 的实例数时,我只需添加一个 ObjectCounter 作为私有成员变量到那个 class:

class SomeUserClass
{
public:
   SomeUserClass() : _userData(0) {/* empty */}
   ~SomeUserClass() {/* empty */}

private:
   ObjectCounter _objectCounter;
   int64_t _userData;
};

(本来我会用 class subclass ObjectCounter 代替,但那样做就是制作 my DOxygen class-graphs unnecessarily complex,所以我改成了私有成员变量)

今天我注意到将此“空”私有成员变量添加到我的 class 对象中通常会增加 class 对象的大小(如 sizeof() 所报告)。例如,以下代码显示当我包含 _objectCounter 成员变量时 sizeof(SomeUserClass) 在我的机器上从 8 增加到 16:

int main(int, char **)
{
   SomeUserClass sc1;
   printf("sizeof(SomeUserClass)=%zu\n", sizeof(SomeUserClass));
   printf("sizeof(ObjectCounter)=%zu\n", sizeof(ObjectCounter));

   return 0;
}

无论是否启用优化(通过 -O3)都会发生增加。

我相信这是因为编译器正在为 _objectCounter 成员变量分配 space,这样如果其他代码需要获取指向 ObjectCounter 的指针,可以提供唯一的地址。但是我的程序中没有任何代码真正引用了 _objectCounter 变量;它的存在只是为了在适当的时候执行它自己的默认构造函数和析构函数。

鉴于此,是否有任何方法可以鼓励(或更好地强制)编译器不为此成员变量分配任何 space?

如果您可以使用 C++20,则可以使用属性 [[no_unique_address]] 来完成此操作。使用

#include <cstdio>
#include <cstdint>

int _objectCount = 0;  // global

class ObjectCounter
{
public:
   ObjectCounter() {printf("DefaultCtor:  count=%i\n", ++_objectCount);}
   ~ObjectCounter() {printf("Dtor:  count=%i\n", --_objectCount);}
};

class SomeUserClass
{
public:
   SomeUserClass() : _userData(0) {/* empty */}
   ~SomeUserClass() {/* empty */}

private:
   [[no_unique_address]] ObjectCounter _objectCounter;
   int64_t _userData;
};

int main(int, char **)
{
   SomeUserClass sc1;
   printf("sizeof(SomeUserClass)=%zu\n", sizeof(SomeUserClass));
   printf("sizeof(ObjectCounter)=%zu\n", sizeof(ObjectCounter));

   return 0;
}

输出:

DefaultCtor:  count=1
sizeof(SomeUserClass)=8
sizeof(ObjectCounter)=1
Dtor:  count=0