在C++中构造hack等价物

Jon*_*set 22 c++ arrays struct flexible-array-member c++11

结构hack你有一个长度为0的数组作为C90和C99的结构的最后一个成员是众所周知的,并且随着C99中灵活的数组成员的引入,我们甚至得到了使用它的标准化方法[].不幸的是,C++没有提供这样的构造,并且(至少使用Clang 3.4),使用[0]或编译结构或[]将产生编译警告--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];
Run Code Online (Sandbox Code Playgroud)

和类似的

$ 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[];
Run Code Online (Sandbox Code Playgroud)

我的问题是这个; 说我希望有一个包含可变大小数组的结构作为C++中的最后一项.给定支持两者的编译器,做什么是正确的?我应该使用struct hack [0](编译器扩展)还是FAM [](这是C99功能)?据我所知,要么会奏效,但我想弄明白哪个是较小的邪恶?

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

其他地方也有类似的问题,但没有一个能回答这个问题:

rod*_*igo 10

这是C++,因此模板可用:

template <int N>
struct hack {
    int filler;
    int thing [N];
};
Run Code Online (Sandbox Code Playgroud)

因此,在不同实例的不同指针之间进行转换将是一个难题.

  • 非常简洁的解决方案,但它需要在编译时知道`N`,但不一定是这种情况.假设我有一个结构,为机器上的每个用户保存一个`double`; 这是你只能在运行时确定的东西. (12认同)

Jam*_*nze 10

您可以使用成员函数获得或多或少相同的效果,并且reinterpret_cast:

int* buffer() { return reinterpret_cast<int*>(this + 1); }
Run Code Online (Sandbox Code Playgroud)

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

struct Hack
{
    char size;
    int* buffer() { return reinterpret_cast<int*>(this + 1); }
};
Run Code Online (Sandbox Code Playgroud)

可能会返回错误对齐的指针.您可以通过将结构中的数据放入与要返回其指针的类型的联合中来解决此问题.如果你有C++ 11,你可以声明:

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

(我想.我从来没有尝试过这个,我可能会有一些错误的语法细节.)

这个成语有第二个重要缺陷:它没有做任何事情来确保大小字段对应于缓冲区的实际大小,更糟糕的是,这里没有真正的使用方法new.要在某种程度上纠正这一点,您可以定义一个特定的类, operator new并且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); }
};
Run Code Online (Sandbox Code Playgroud)

然后,客户端代码必须使用placement new来分配:

Hack* hack = new (20) Hack(20);
Run Code Online (Sandbox Code Playgroud)

客户端仍然需要重复大小,但他不能忽略它.

还有一些技术可用于防止创建未动态分配的实例等,最终得到如下结果:

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); }
};
Run Code Online (Sandbox Code Playgroud)

鉴于这类人员的根本危险,如果有必要采取这么多保护措施,那就值得商榷.即使有它们,它实际上只有完全理解所有约束的人才能使用,并且正在仔细关注.在极端情况下,在非常低级别的代码中,您只需要创建缓冲区std::vector<int>并完成它.除了最低级别的代码外,性能差异不值得冒险和努力.

编辑:

作为一个例子,g ++的实现 std::basic_string使用了与上面非常相似的东西,其中struct包含一个引用计数,当前大小和当前容量(三个size_t),后面直接跟着字符缓冲区.因为它早在C++ 11和alignas/ 之前编写alignof, std::basic_string<double>就会在某些系统上崩溃(例如Sparc).(虽然从技术上讲是一个错误,但大多数人并不认为这是一个关键问题.)

  • 为什么`+ 1`最好用`sizeof(Hack)`代替?以及有关“ new(20)Hack(20);”的问题,由于您需要键入两次相同值的值,因此更好的解决方案是返回“ new(20)Hack(20);”的`newMe(int)`。 (2认同)

Dav*_*eas 8

首先想到的是DO Not,不要在C++中编写C语言.在99.99%的情况下,这hack不是必需的,与仅仅持有a相比,性能不会有任何明显的改善,std::vector并且会使您和您部署此项目的项目的其他维护者的生活复杂化.

如果你想要一个标准的兼容方法,提供一个包装类型,它动态地分配一块足够大的内存,以包含hack(减去数组)加上数组N*sizeof(int)的等价物(不要忘记确保适当的附加).该类将具有将成员和数组元素映射到内存中正确位置的访问器.

忽略对齐和锅炉板代码以使界面更好并且实现安全:

template <typename T>
class DataWithDynamicArray {
   void *ptr;
   int* array() {
      return static_cast<int*>(static_cast<char*>(ptr)+sizeof(T)); // align!
   }
public:
   DataWithDynamicArray(int size) : ptr() {
      ptr = malloc(sizeof(T) + sizeof(int)*size); // force correct alignment
      new (ptr) T();
   }
   ~DataWithDynamicArray() { 
      static_cast<T*>(ptr)->~T();
      free(ptr);
   }
// copy, assignment...
   int& operator[](int pos) {
       return array()[pos];
   }
   T& data() {
      return *static_cast<T*>(ptr);
    }
};

struct JustSize { int size; };
DataWithDynamicArray<JustSize> x(10);
x.data().size = 10
for (int i = 0; i < 10; ++i) {
    x[i] = i;
}
Run Code Online (Sandbox Code Playgroud)

现在我真的不会那样实现它(我会避免实现它!),例如,大小应该是DataWithDynamicArray... 的状态的一部分...

这个答案仅作为一个练习提供,以解释同样的事情可以在没有扩展的情况下完成,但要注意这只是一个玩具示例,它有很多问题,包括但不限于异常安全或对齐(并且比强制执行更好)用户malloc使用正确的大小).你事实上可以并不意味着你应该和真正的问题是,你是否需要此功能,以及是否你正在尝试做的是在全部或没有一个好的设计.

  • 这与@ JamesKanze的答案非常相似,只是它还增加了一个间接级别,我想避免.我喜欢它维护数组索引语义的事实.丢弃的另一件事是,在结构中使用`[]`或`[0]`告诉任何读取代码的人,实际上会有与结构直接相关的结构后面的数据(它甚至有一个名字) ). (2认同)