C ++中的Struct hack等效项 [英] Struct hack equivalent in C++

查看:86
本文介绍了C ++中的Struct hack等效项的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

众所周知,您有一个长度为0的数组作为C90和C99中结构的最后一个成员的结构技巧,并且随着C99中灵活数组成员的引入,我们甚至有了使用它的标准化方法[].不幸的是,C ++没有提供这样的构造,并且(至少使用Clang 3.4 ),使用[0][]编译结构会产生--std=c++11 -pedantic编译警告:

The struct hack where you have an array of length 0 as the last member of a struct from C90 and C99 is well known, and with the introduction of flexible array members in C99, we even got a standardized way of using it with []. Unfortunately, C++ provides no such construct, and (at least with Clang 3.4), compiling a struct with either [0] or [] will yield a compilation warning with --std=c++11 -pedantic:

$ cat test.cpp 
struct hack {
  char filler;
  int things[0];
};
$ clang++ --std=c++11 -pedantic test.cpp
\test.cpp:3:14: warning: zero size arrays are an extension [-Wzero-length-array]
  int things[0];

类似

$ cat test.cpp 
struct fam {
  char filler;
  int things[];
};
$ clang++ --std=c++11 -pedantic test.cpp
\test.cpp:3:7: warning: flexible array members are a C99 feature [-Wc99-extensions]
  int things[];

然后我的问题是;说我想拥有一个包含可变大小数组的结构作为C ++中的最后一项.对于同时支持两者的编译器,正确的做法是什么?我应该使用struct hack [0](这是编译器扩展)还是FAM [](这是C99功能)?据我了解,这两者都可以使用,但是我试图找出哪个是较小的邪恶?

My question then is this; say that I want to have a struct that contains an array of variable size as the last item in C++. What is the right thing to do given a compiler that supports both? Should I go with the struct hack [0] (which is a compiler extension), or the FAM [] (which is a C99 feature)? As far as I understand it, either will work, but I am trying to figure out which is the lesser evil?

此外,在人们开始建议将int*保留在结构中单独分配的内存之前,这并不是一个令人满意的答案.我想分配一块内存来容纳我的结构和数组元素.使用std :: vector也属于同一类别.如果您想知道为什么我不想使用指针,请参阅 R.对另一个问题的答案提供了很好的概述.

Also, before people start suggesting keeping an int* to a separately allocated piece of memory in the struct instead, that is not a satisfactory answer. I want to allocate a single piece of memory to hold both my struct and the array elements. Using a std::vector also falls into the same category. If you wonder why I don't want to use a pointer instead, the R.'s answer to another question gives a good overview.

在其他地方也有类似的问题,但是没有一个人可以回答这个特定问题:

There have been some similar questions elsewhere, but none give an answer to this particular question:

  • Are flexible array members valid in C++?: Very similar, but the question there is whether FAM is valid in C++ (no). I am looking for a good reason to pick one or the other.
  • Conforming variant of the old "struct hack": Proposes an alternative, but it's neither pretty, nor always correct (what if padding is added to the struct?). Accessing the elements later is also not as clean as doing e.things[42].

推荐答案

使用成员可以或多或少地获得相同的效果 函数和reinterpret_cast:

You can get more or less the same effect using a member function and a reinterpret_cast:

int* buffer() { return reinterpret_cast<int*>(this + 1); }

这有一个主要缺陷:它不能保证正确 结盟.例如,类似:

This has one major defect: it doesn't guarantee correct alignment. For example, something like:

struct Hack
{
    char size;
    int* buffer() { return reinterpret_cast<int*>(this + 1); }
};

可能返回未对齐的指针.你可以解决 通过将数据放入结构中并与类型进行联合 您要返回的指针.如果您拥有C ++ 11,则可以 声明:

is likely to return a mis-aligned pointer. You can work around this by putting the data in the struct in a union with the type whose pointer you are returning. If you have C++11, you can declare:

struct alignas(alignof(int)) Hack
{
    char size;
    int* buffer() { return reinterpret_cast<int*>(this + 1); }
};

(我想.我从未真正尝试过,我可以尝试一些 语法错误的详细信息.)

(I think. I've never actually tried this, and I could have some details of the syntax wrong.)

这个成语还有第二个重要缺陷:它无济于事 确保size字段对应于 缓冲区,更糟糕的是,这里没有使用new的真正方法.到 更正此问题,您可以定义特定于类的 operator newoperator delete:

This idiom has a second important defect: it does nothing to ensure that the size field corresponds to the actual size of the buffer, and worse, there is no real way of using new here. To correct this, somewhat, you can define a class specific operator new and operator delete:

struct alignas(alignof(int)) Hack
{
    void* operator new( size_t, size_t n );
    void operator delete( void* );
    Hack( size_t n );
    char size;
    int* buffer() { return reinterpret_cast<int*>(this + 1); }
};

然后,客户代码将必须使用new布局进行分配:

The client code will then have to use placement new to allocate:

Hack* hack = new (20) Hack(20);

客户仍然必须重复大小,但是他不能忽略 它.

The client still has to repeat the size, but he cannot ignore it.

还有一些可用于防止创建的技术 没有动态分配等的实例最终 像这样:

There are also techniques which can be used to prevent creating instances which aren't allocated dynamically, etc., to end up with something like:

struct alignas(alignof(int)) Hack
{
private:
    void operator delete( void* p )
    {
        ::operator delete( p );
    }
    //  ban all but dynamic lifetime (and also inheritance, member, etc.)
    ~Hack() = default;

    //  ban arrays
    void* operator new[]( size_t ) = delete;
    void operator delete[]( void* p ) = delete;
public:
    Hack( size_t n );
    void* operator new( size_t, size_t n )
    {
        return ::operator new( sizeof(Hack) + n * sizeof(int) );
    }
    char size;
    //  Since dtor is private, we need this.
    void deleteMe() { delete this; }
    int* buffer() { return reinterpret_cast<int*>(this + 1); }
};

鉴于此类职业的基本危险,这值得商bat 如果有必要采取许多保护措施.即使有他们 只有真正了解所有内容的人才能使用它 约束,并正在认真注意.除了 极端情况下,在非常低级的代码中,您只需 缓冲std::vector<int>并完成它.除了 最低级别的代码,性能上的差异不会 值得冒险和努力.

Given the fundamental dangers of such a class, it is debatable if so many protective measures are necessary. Even with them, it's really only usable by someone who fully understands all of the constraints, and is carefully paying attention. In all but extreme cases, in very low level code, you'd just make the buffer a std::vector<int> and be done with it. In all but the lowest level code, the difference in performance would not be worth the risk and effort.

作为示例,g ++的实现 std::basic_string使用与上面非常相似的内容, struct包含参考计数,即当前大小 和当前容量(三个size_t),然后直接跟 字符缓冲区.而且因为它是很久以前写的 C ++ 11和alignas/alignof,类似 std::basic_string<double>在某些系统上会崩溃(例如 Sparc). (虽然从技术上讲是一个错误,但大多数人并不认为 这是一个关键问题.)

As a point of example, g++'s implementation of std::basic_string uses something very similar to the above, with a struct containing a reference count, the current size and the current capacity (three size_t), followed directly by the character buffer. And since it was written long before C++11 and alignas/alignof, something like std::basic_string<double> will crash on some systems (e.g. a Sparc). (While technically a bug, most people do not consider this a critical problem.)

这篇关于C ++中的Struct hack等效项的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆