6 回答
您可以使用成员函数和 a 获得或多或少相同的效果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 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); }
};
然后客户端代码将不得不使用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); }
};
鉴于此类的基本危险,是否需要采取如此多的保护措施是值得商榷的。即使有它们,它也只有完全了解所有限制并仔细注意的人才能使用。在除极端情况外的所有情况下,在非常低级的代码中,您只需将缓冲区设为 astd::vector<int>
并完成它。除了最低级别的代码之外,在所有代码中,性能差异都不值得冒险和努力。
编辑:
举个例子,g++ 的实现
std::basic_string
使用了与上面非常相似的东西,其中struct
包含一个引用计数、当前大小和当前容量(三个size_t
),然后是字符缓冲区。而且由于它是早在 C++11 和alignas
/之前编写的alignof
,因此类似的东西
std::basic_string<double>
会在某些系统(例如 Sparc)上崩溃。(虽然从技术上讲是一个错误,但大多数人并不认为这是一个关键问题。)
这是 C++,所以模板可用:
template <int N>
struct hack {
int filler;
int thing [N];
};
那么,在指向不同实例的不同指针之间进行转换将是一个难题。
首先想到的是不要,不要用 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;
}
现在我真的不会那样实现它(我会完全避免实现它!!),例如大小应该是DataWithDynamicArray
...状态的一部分
此答案仅作为练习提供,以解释无需扩展即可完成相同的操作,但请注意,这只是一个玩具示例,存在许多问题,包括但不限于异常安全或对齐(但比强制用户malloc
使用正确的尺寸进行操作)。你可以的事实并不意味着你应该,真正的问题是你是否需要这个功能,以及你想要做的是否是一个好的设计。
如果你真的觉得需要使用 hack,为什么不直接使用
struct hack {
char filler;
int things[1];
};
其次是
hack_p = malloc(sizeof(struct hack)+(N-1)*sizeof int));
或者甚至不用为 -1 操心,多住一点空间。
C++ 没有“灵活数组”的概念。在 C++ 中拥有灵活数组的唯一方法是使用动态数组 - 这会导致您使用int* things
. 如果您尝试从文件中读取此数据,您将需要一个大小参数,以便您可以创建适当大小的数组(或使用 astd::vector
并继续读取直到到达流的末尾)。
“灵活数组” hack 保留了空间局部性(即在结构的其余部分的连续块中分配的内存),当您被迫使用动态内存时您会丢失。没有真正优雅的解决方法(例如,您可以分配一个大缓冲区,但您必须使其足够大以容纳您想要的任意数量的元素 - 并且如果正在读取的实际数据小于缓冲区,将分配浪费的空间)。
此外,在人们开始建议将 int* 保留到结构中单独分配的一块内存之前,这不是一个令人满意的答案。我想分配一块内存来保存我的结构和数组元素。使用 std::vector 也属于同一类别。
这就是您在 C++ 中执行此操作的方式。您可以随心所欲地对它投反对票,但事实仍然存在:当您迁移到不支持它的编译器时,非标准扩展将不起作用。如果您遵守标准(例如避免使用特定于编译器的技巧),您就不太可能遇到这些类型的问题。
当编译器clang时,灵活的数组成员至少有一个优势超过零长度数组。
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;
};