在C ++中构造hack等价物

时间:2013-11-29 16:47:42

标签: c++ arrays c++11 struct flexible-array-member

结构hack你有一个长度为0的数组作为C90和C99结构的最后一个成员是众所周知的,并且随着C99中灵活数组成员的引入,我们甚至得到了一个标准化的方法来使用它[]。遗憾的是,C ++没有提供这样的构造,并且(至少使用Clang 3.4 ),使用[0][]编译结构将产生{{1}的编译警告}:

--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];

我的问题是这个;说我希望有一个包含可变大小数组的结构作为C ++中的最后一项。给定支持两者的编译器,做什么是正确的?我应该使用struct hack $ 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[]; (这是编译器扩展)还是FAM [0](这是C99功能)?据我了解,两者都可行,但我想弄清楚哪个是较小的邪恶?

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

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

6 个答案:

答案 0 :(得分:10)

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

template <int N>
struct hack {
    int filler;
    int thing [N];
};

然后,在不同实例的不同指针之间进行转换将是一个难题。

答案 1 :(得分:10)

使用成员可以获得或多或少相同的效果 功能和reinterpret_cast

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

这有一个主要缺陷:它不能保证正确 对准。例如,像:

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

可能会返回错误对齐的指针。你可以解决 这是通过将结构中的数据放入与类型的并集中 你要返回的指针。如果你有C ++ 11,你可以 声明:

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

(我想。我从来没有尝试过这个,我可以有一些 语法细节错误。)

这个成语有第二个重要缺陷:它没有任何作用 确保size字段对应的实际大小 缓冲区,更糟糕的是,这里没有使用new的真正方法。至 更正,在某种程度上,您可以定义一个特定的类 operator newoperator 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); }
};

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

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

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

还有一些技术可用于防止创建 最终动态分配的实例等 有类似的东西:

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

考虑到这样一个阶级的根本危险,这是值得商榷的 如果有这么多保护措施是必要的。即使有他们, 它真的只有完全理解所有人的人才能使用 约束,并正在认真关注。除此之外 极端情况下,在非常低级别的代码中,你只需要制作 缓冲一个std::vector<int>并完成它。除了所有 最低级代码,性能差异不会 值得冒风险和努力。

编辑:

作为一个例子,g ++的实现 std::basic_string使用与上述非常类似的内容, 含有引用计数struct的当前大小 和当前的容量(三size_t),然后直接 字符缓冲区。因为很久以前就已经写好了 C ++ 11和alignas / alignof之类的东西 std::basic_string<double>会在某些系统上崩溃(例如 一个Sparc)。 (虽然技术上是一个错误,但大多数人都不会考虑 这是一个关键问题。)

答案 2 :(得分:8)

首先想到的是 DO Not ,不要在C ++中编写C语言。在99.99%的情况下,不需要这个hack,与仅仅持有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;
}

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

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

答案 3 :(得分:4)

如果您真的觉得需要使用黑客,为什么不使用

struct hack {
  char filler;
  int things[1];
};

接着是

hack_p = malloc(sizeof(struct hack)+(N-1)*sizeof int));

或者甚至不打扰-1并且有一点额外的空间。

答案 4 :(得分:3)

C ++没有“灵活数组”的概念。在C ++中使用灵活数组的唯一方法是使用动态数组 - 这会导致您使用int* things。如果您尝试从文件中读取此数据,则需要一个size参数,以便您可以创建适当大小的数组(或使用std::vector并继续阅读,直到到达流的末尾。)< / p>

“灵活阵列”hack保持空间局部性(即在与结构的其余部分相连的块中具有分配的内存),当您被迫使用动态内存时,这会丢失。实际上没有一种优雅的方式(例如,您可以分配一个大缓冲区,但是您必须使其足够大以容纳您想要的任意数量的元素 - 并且如果读入的实际数据小于缓冲区,会浪费空间分配)。

  

此外,在人们开始建议单独保留int *之前   在结构中分配了一块内存,而不是   满意的答案。我想分配一块内存   保持我的结构和数组元素。也使用std :: vector   属于同一类别。

就像你在C ++中那样做。您可以根据需要对其进行投票,但事实仍然是:当您转移到不支持它的编译器时,非标准扩展不会起作用。如果你遵守标准(例如避免使用编译器特定的黑客攻击),你就不太可能遇到这些类型的问题。

答案 5 :(得分:1)

当编译器被扼杀时,灵活数组成员在零长度数组上至少有一个优势。

struct Strukt1 {
    int fam[];
    int size;
};

struct Strukt2 {
    int fam[0];
    int size;
};

如果clang看到Strukt1,则会出错,但如果它看到Strukt2则会出错。在任何一种情况下,gcc和icc都可以接受无错误和msvc错误。如果代码编译为C,则gcc会出错。

同样适用于这个相似但不太明显的例子:

struct Strukt3 {
    int size;
    int fam[];
};

strukt Strukt4 {
    Strukt3 s3;
    int i;
};
相关问题