由于放置新用法而禁止虚拟功能的体面方式

时间:2011-06-21 06:18:34

标签: c++

在编译/运行时检查特定结构/类没有任何虚函数的方法是什么。执行贴装新时,需要进行此检查以确保正确的字节对齐。

拥有一个虚拟函数可以将整个数据移动一个 vtable 指针大小,这将与放置新操作符一起完全搞乱。


更多细节:我需要适用于所有主要编译器和平台的内容,例如: VS2005,VC ++ 10,GCC 4.5和Sun Studio 12.1在Windows,Linux和Solaris之上。

使用以下方案确保可以正常工作的东西应该足够了:

struct A { char c; void m(); };
struct B : A { void m(); };

如果有人决定进行此更改:

struct A { char c; virtual void m(); };
struct B : A { void m(); };

看到struct A must not contain virtual functions.

的编译时错误会很棒

6 个答案:

答案 0 :(得分:7)

有一些设施和技巧(取决于你使用的C ++版本)来获得一个类的正确对齐。

在C ++ 0x中,alignof命令与sizeof类似,但返回所需的对齐方式。

在C ++ 03中,首先要注意的是大小是对齐的倍数,因为元素在数组中需要是连续的。这意味着使用大小作为对齐是过度热心(并可能浪费空间)但工作正常。通过一些技巧,您可以获得更好的价值:

template <typename T>
struct AlignHelper
{
  T t;
  char c;
};

template <typename T>
struct Alignment
{
  static size_t const diff = sizeof(AlignHelper<T>) - sizeof(T);
  static size_t const value = (diff != 0) ? diff : sizeof(T);
};

这个小帮助器提供了正确的对齐作为编译时常量(因此适用于模板编程)。它可能大于所需的最小对齐(*)。

通常情况下,使用placement new可能没问题,除非您实际在“原始缓冲区”上使用它。在这种情况下,缓冲区的大小应使用以下公式确定:

// C++03
char buffer[sizeof(T) + alignof(T) - 1];

或者您应该使用C ++ 0x工具:

// C++0x
std::aligned_storage<sizeof(T), alignof(T)> buffer;

确保虚拟表“正确”对齐的另一个技巧是使用联合:

// C++03 and C++0x
union { char raw[sizeof(T)]; void* aligner; } buffer;

aligner参数保证buffer正确地与指针对齐,因此对于虚拟表指针也是如此。

编辑:@Tony建议的其他解释。

(*)这是如何工作的?

要理解它,我们需要深入研究类的内存表示。类的每个子元素都有自己的对齐要求,例如:

struct A { int a; char b; int c; };

+----+-+---+----+
| a  |b|xxx| c  |
+----+-+---+----+

其中xxx表示添加了填充,以便c适当对齐。

A的对齐方式是什么?一般来说,它是子元素的更严格的对齐,所以在这里,int的对齐(通常4,因为int通常是32位积分。

为了“猜测”任意类型的对齐,我们因此使用AlignHelper模板“欺骗”编译器。请记住,sizeof(AlignHelper<T>)必须是对齐的倍数,因为类型应该在数组中连续布局,因此我们希望我们的类型将在c属性之后填充,并且对齐将是c(根据定义为1)加上填充的大小。

// AlignHelper<T>
+----------------+-+---+
|        t       |c|xxx|
+----------------+-+---+

// T
+----------------+
|        t       |
+----------------+

当我们sizeof(AlignHelper<T>) - sizeof(T)时,我们会发现这种差异。但令人惊讶的是,它可能是0

问题来自这样一个事实:如果在T的末尾有一些填充(未使用的字节),那么智能编译器可以决定在那里隐藏c,从而缩小大小将是0

显然,我们可以尝试递归地增加c属性的大小(使用char数组),直到我们最终获得非零差异。在这种情况下,我们会得到一个“紧密”对齐,但最简单的方法是拯救并使用sizeof(T),因为我们已经知道它是对齐的倍数。

最后,无法保证我们使用此方法获得的对齐是T的对齐,我们得到它的倍数,但它可能更大,因为sizeof是依赖于实现的例如,编译器可以决定将所有类型与2个边界的幂对齐。

答案 1 :(得分:3)

  

什么是一个体面的方法   在编译/运行时检查a   特定的struct / class没有   任何虚拟功能

template<typename T>
struct Is_Polymorphic
{
  struct Test : T { virtual ~Test() = 0; };
  static const bool value = (sizeof(T) == sizeof(Test));
};

以上class可以帮助您检查编译时given class is polymorphic or not 是否。 [注意:virtual继承也包含vtable]

答案 2 :(得分:2)

dynamic_cast仅允许polymorphic classes,因此您可以将其用于编译时检查。

答案 3 :(得分:1)

使用tr1中的is_pod类型特征?

答案 4 :(得分:1)

你几乎肯定做错了什么。

但是,鉴于您决定做错事,您不想知道您的tpe是否没有虚函数。您想知道将类型视为字节数组是否可以。

在C ++ 03中,你的类型是POD吗?幸运的是,有一个特点,恰如其分地命名为is_pod<T>。这是由C ++ 03中的Boost / TR1提供的,尽管它需要一个相对现代的编译器[gcc&gt; 4.3,MSVC&gt; 8,其他我不知道]。

在C ++ 11中,您可以通过询问您的类型是否可以轻松复制来简化您的要求。同样,有一个特点:is_trivially_copyable<T>

在任何一种情况下,也有is_polymorphic<T>,但正如我所说的,无论如何,这真的不是你想要的。如果您使用的是较旧的编译器,如果从Boost获得它,它确实具有开箱即用的优势;它会执行其他地方提到的sizeof测试,而不是像false那样只为所有用户定义的类型报告is_pod

无论如何,你最好120%确定你的构造函数是noop;这不是可以验证的东西。


我刚看到你的编辑。在您列出的内容中,Sun Studio是唯一可能没有必要的内在函数来使用这些特性的工具。 gcc和MSVC已经有好几年了。

答案 5 :(得分:0)

您无法确定某个类是否具有虚函数。