我见过以下代码:
class SomeClass
{
public:
int mA;
int mB;
int mC;
int mD;
int operator[] (const int index) const
{
return ((int*)(this))[index];
}
};
这是如何工作的?我知道this关键字是指向这个类的指针,但没有知道有多少变量的属性...我们怎样才能安全地访问"这个"指针?
答案 0 :(得分:4)
不建议使用此类型的东西,因为C ++标准无法保证。但是,大多数编译器都明确定义了它们的内存布局行为(通常基于每个架构)并提供#pragma
来操纵打包行为(例如MSVC中的#pragma pack
)。如果您了解/利用这些功能,则可以使其适用于大多数给定的编译器/体系结构。 但是,它不可移植!对于每个新编译器,您需要重新测试和调整,这是一项昂贵的维护任务。一般来说,我们更喜欢更便于携带。
如果您真的想这样做,可以添加static_assert
来验证编译器的行为。
int operator[] (const int index) const
{
static_assert(sizeof(SomeClass) == 4 * sizeof(mA), "Padding not supported");
return ((int*)(this))[index];
}
由于标准不允许对成员进行重新排序,从逻辑上讲,如果SomeClass
的大小为16,我们可以推断出这个代码将按预期工作。有了断言,我们至少会收到通知,如果有人在不同的编译器上构建它并尝试填充它(从而弄乱我们)。
但是,我们可以符合标准并获得阵列插槽的名称。您可以考虑使用以下模式:
class SomeClass
{
enum Index {
indexA,
indexB,
indexC,
indexD,
indexCount;
};
int mData[indexCount];
public:
int operator[] (const int index) const
{
return mData[index];
}
int& A() { return mData[indexA]; }
int& B() { return mData[indexB]; }
int& C() { return mData[indexC]; }
int& D() { return mData[indexD]; }
};
这提供了类似的功能,但是由C ++标准保证。
答案 1 :(得分:0)
这个“有效”只是因为它是未定义的行为。编译器为mB
选择的内存位置恰好与&mA + 1
相同,mC
的地址为&mA + 2
和&mB + 1
,依此类推。
然而,类确实是'标准布局'(所有数据成员都具有相同的访问说明符,并且没有使用继承[n3337§9p7]),传统上它确实产生了成员变量的这种组织。虽然不能保证。 “标准布局”可能意味着其他东西,例如成员之间的一些填充[n3337§9.2p14],但我怀疑是否有任何平台实际上做到了这一点。
规范确实说将地址放在对象之后是合法的,因此计算&mA + 1
是合法的。 [n3337§5.7p5]并且规范还说明了一个具有正确类型和值的指针确实指向一个对象,无论指针是如何计算的。
如果类型T的对象位于地址A,则类型为cv T *的指针(其值为地址A)被称为指向该对象,而不管该值是如何获得的。 [注意:例如,超过数组末尾的地址(5.7)将被视为指向可能位于该地址的数组元素类型的无关对象。 - [n3337§3.9.2p3]
在回顾§5.9和§5.10之后,我认为以下可能在技术上是合法的,尽管它可能有未指明的行为:
if (&mA + 1 == &mB && &mB + 1 == &mC && &mC + 1 == &mD) {
return ((int*)(this))[index];
}
即使是演员也是合法的,因为规范说标准布局类可以转换为指向其第一个成员的指针。 [n3337§9.2p20]