开发者

Decent way to disallow virtual functions due to placement new usage

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.

Somet开发者_JAVA百科hing 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.


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.


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]


You are almost certainly doing something wrong.

However, given that you have decided to do something wrong, you don't want to know if your tpe has no virtual functions. You want to know if it is okay to treat your type as an array of bytes.

In C++03, is your type POD? As luck would have it, there's a trait for that, aptly named is_pod<T>. This is provided by Boost/TR1 in C++03, although it requires a relatively modern compiler [gcc > 4.3, MSVC > 8, others I don't know].

In C++11, you can ease up your requirements by asking if your type is trivially copiable. Again, there's a trait for that: is_trivially_copyable<T>.

In either case, there is also is_polymorphic<T>, but as I said, that's really not what you want anyhow. If you are using an older compiler, it does have the advantage of working out of the box if you get it from Boost; it performs the sizeof test mentioned elsewhere, rather than simply reporting false for all user defined types as is the case with is_pod.

No matter what, you'd better be 120% sure your constructor is a noop; that's not something that can be verified.


I just saw your edit. Of what you listed, Sun Studio is the only one that might not have the necessary intrinsics for these traits to work. gcc and MSVC have both had them for several years now.


dynamic_cast is only allowed for polymorphic classes, so you can utilize that for a compile time check.


Use is_pod type trait from tr1?


There is no feature for you to determine whether a class has virtual functions are not.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜