Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decent way to disallow virtual functions due to placement new usage

Tags:

c++

What would be a decent approach to check at compile/run-time that a particular struct/class does not have any virtual functions. This check is required in order to ensure the proper byte alignment when doing placement new.

Having so much as a single virtual function will shift the entire data by a vtable pointer size, which will completely mess things up in conjunction with the placement new operator.


Some more details: I need something that works across all major compiler and platforms, e.g. VS2005, VC++10, GCC 4.5, and Sun Studio 12.1 on top of Windows, Linux, and Solaris.

Something that is guaranteed to work with the following scenario should suffice:

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

Should someone decide to make this change:

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

It would be great to see a compile-time error that says struct A must not contain virtual functions.

like image 235
Nick Avatar asked Jun 21 '11 06:06

Nick


2 Answers

There are facilities and tricks (depending on the version of C++ you are using) to get the proper alignment for a class.

In C++0x, the alignof command is similar to sizeof but returns the required alignment instead.

In C++03, the first thing to note is that the size is a multiple of the alignment, because elements need be contiguous in an array. This means that using the size as the alignment is over-zealous (and may waste space) but works fine. With some trickery you can get a better value:

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

This little helper gives a correct alignment as a compile-time constant (suitable for template programming therefore). It may be larger than the minimal alignment required (*).

Normally though it should be fine to use placement new, unless you are actually using it on a "raw buffer". In this case, the size of the buffer should be determined with the following formula:

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

Or you should make use of C++0x facilities:

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

Another trick to ensure a "right" alignment for virtual tables it to make use of the union:

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

The aligner parameter guarantees that the buffer is correctly aligned for pointers, and thus for virtual tables pointers as well.

EDIT: Additional explanations as suggested by @Tony.

(*) How does this work ?

To understand it we need to delve into the memory representation of a class. Each subelement of a class has its own alignment requirement, so for example:

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

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

Where xxx denotes padding added so that c is suitably aligned.

What is the alignment of A ? Generally speaking, it is the stricter alignment of the subelements, so here, the alignment of int (which is often 4 since int is often a 32 bits integral).

To "guess" the alignment of an arbitrary type, we thus "trick" the compiler by using the AlignHelper template. Remember that sizeof(AlignHelper<T>) must be a multiple of the alignment because types should be laid out contiguously in an array, thus we hope our type will be padded after the c attribute, and the alignment will be the size of c (1 by definition) plus the size of the padding.

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

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

When we do sizeof(AlignHelper<T>) - sizeof(T) we get this difference. Surprisingly though, it could be 0.

The issue comes from the fact that if there is some padding (unused bytes) at the end of T, then a smart compiler could decide to stash c there, and thus the difference of size would be 0.

We could, obviously, try to recursively increase the size of c attribute (using a char array), until we finally get a non-zero difference. In which case we would get a "tight" alignment, but the simplest thing to do is to bail out and use sizeof(T), since we already know it is a multiple of the alignment.

Finally, there is no guarantee that the alignment we get with this method is the alignment of T, we get a multiple of it, but it could be bigger, since sizeof is implementation dependent and a compiler could decide to align all types on power of 2 boundaries, for example.

like image 171
Matthieu M. Avatar answered Oct 07 '22 12:10

Matthieu M.


What would be a decent approach to check at compile/run-time that a particular struct/class does not have any virtual functions

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

Above class can help you to check if the given class is polymorphic or not at compile time. [Note: virtual inheritance also have a vtable included]

like image 39
iammilind Avatar answered Oct 07 '22 13:10

iammilind