成员指针的大小和 reinterpret_cast 是不是固定?

Posted

技术标签:

【中文标题】成员指针的大小和 reinterpret_cast 是不是固定?【英文标题】:are member pointers fixed in size and reinterpret_cast?成员指针的大小和 reinterpret_cast 是否固定? 【发布时间】:2015-06-10 01:29:34 【问题描述】:

我正在尝试创建一个模板类,其中包含指向任意类实例和函数的指针,如下所示:

template<class C>
class A 
   typedef void (C::*FunctPtr)(); //e.g. void C::some_funct();

   FunctPtr functPtr_;
   C* instPtr_;
public:
   A(FunctPtr functPtr, C* instPtr)
      : functPtr_(functPtr)
      , instPtr_(instPtr) 
;

但是,我希望能够创建此类的实例,而无需使用 Placement new 进行动态内存分配。 C++ 标准是否保证该模板类对于所有 C 类都是固定大小的?

在Don Clugston's article 指针中,我注意到各种编译器上成员函数指针的各种大小的图表,并且一些编译器的大小并不总是相同。我以为我被冲洗了,但这个标准符合吗?从 C++ 标准秒。 5.2.10 重新解释演员表:

——将“指向成员函数的指针”类型的纯右值转换为指向成员函数的不同指针 类型并返回其原始类型会产生指向成员值的原始指针。

C++ 标准中的该语句是否表明成员函数指针的大小都相同?

如果不是,我想我仍然可以重写代码,以明确利用 reinterpret_cast 保证:

class GenericClass;

template<class C>
class A 

   typedef void (GenericClass::*GenFunctPtr)();
   typedef void (C::*SpecificFunctPtr)();

   GenFunctPtr functPtr_; //store any kind of function ptr in this fixed format
   GenericClass* instPtr_;

public:
   A(SpecificFunctPtr functPtr, C* instPtr)
      : functPtr_(reinterpret_cast<GenFunctPtr>(functPtr))
      , instPtr_(reinterpret_cast<GenericClass*>(instPtr)) 

   void DoSomething()
   
      //now convert pointers back to the original type to use...
      reinterpret_cast<SpecificFunctPtr>(functPtr_);
      reinterpret_cast<C*>(instPtr_);
   
;

现在看来,这似乎需要尺寸相同但符合标准,对吧?我更喜欢第一个选项,但是如果我必须第二个也可以。想法?

【问题讨论】:

该标准甚至不保证 C* 对于所有 C 的大小相同(尽管它在必须的平台上)——即使它保证您可以通过 void * 往返.我还认为它允许任何插入的填充发生变化。如果您想要完全的可移植性,我认为您不走运 - 尽管我认为您的机制将适用于大多数平台。 【参考方案1】:

我不知道标准中是否指定了指向成员的实现细节(找不到),但是由于我们知道C*对于所有C都有相同的大小,我们可以确定FunctPtr 的大小对于各种类型C 并添加一个static_assert

template <class C>
class A 
   typedef void (C::*FunctPtr)(); //e.g. void C::some_funct();
   static_assert(sizeof(FunctPtr) == 16, 
       "unexpected FunctPtr size"); // from coliru, clang and gcc

   FunctPtr functPtr_;
   C* instPtr_;
   ...
;

至少我尝试了几种类型的类(base,derived,multiple derived x virtual,non-virtual)并且它总是给出相同的大小。

这可能是特定于平台和/或编译器的,正如Pointers to member functions are very strange animals 所指出的那样:

仅使用单继承的类的成员函数指针的大小就是指针的大小。 使用多重继承的类的成员函数指针的大小是指针的大小加上 size_t 的大小。

这不是我在 clang 或 gcc 中看到的。

【讨论】:

使用 static_assert 将是一个很好的双重检查,可能在放置 new 的位置执行以确保实例适合。但是,如果代码符合标准并保证可移植,我会更加一致,而不是确保它与特定编译器兼容。【参考方案2】:

Microsoft 编译器根据类的复杂程度使用不同大小的成员指针。从技术上讲,这是不合规的(因为即使类定义不可见,您也可以定义指向成员的指针),但它在实践中运行良好,默认情况下会这样做。编译指示和编译器开关可用于控制此行为。参见例如https://msdn.microsoft.com/en-us/library/83cch5a6.aspx。

即使任何给定类的指针总是相同大小,只是不同的类可能有不同大小的成员指针。

【讨论】:

Microsoft 编译器并不是最符合标准的。但关于您的第二条陈述:“......不同的类可能有不同大小的指向成员的指针”。这似乎不符合标准,因为标准说您可以在成员指针之间来回使用 reinterpret_cast 强制转换,即使它指向不同的类类型。 我完全同意。我只是指出一种流行的编译器所做的权衡 - 不兼容但默认情况下更有效。我怀疑你巧妙的GenericClass 机制会在那里遇到困难。

以上是关于成员指针的大小和 reinterpret_cast 是不是固定?的主要内容,如果未能解决你的问题,请参考以下文章

转static_cast和reinterpret_cast

reinterpret

reinterpret_cast on char 和 unsigned char 之间的指针陷阱?

reinterpret_cast 在 char* 指针上

reinterpret_cast

如何在编译时验证 reinterpret_cast 的有效性