在内部没有数组的情况下重载 [] 运算符
Overloading the [] operator with no array internally
我看过下面的代码:
class SomeClass
{
public:
int mA;
int mB;
int mC;
int mD;
int operator[] (const int index) const
{
return ((int*)(this))[index];
}
};
这是如何工作的?我知道 this 关键字是指向此 class 的指针,但 属性 不知道有多少变量...我们如何安全地访问 "this" 指针的 [index]?
这 'works' 只是因为它是未定义的行为。编译器为mB
选择的内存位置恰好与&mA + 1
相同,mC
的地址为&mA + 2
和&mB + 1
等等。
然而 class 确实是 'standard layout'(所有数据成员都具有相同的访问说明符并且没有使用继承 [n3337 § 9 p7]),传统上确实会产生这种组织的成员变量。但这并不能保证。 'standard layout' 可能意味着其他含义,例如成员之间的一些填充 [n3337 § 9.2 p14],但我怀疑是否有任何平台真正做到了这一点。
规范确实说通过对象获取地址是合法的,因此计算 &mA + 1
是合法的。 [n3337 § 5.7 p5] 并且规范还说,无论指针是如何计算的,具有正确类型和值的指针都会指向一个对象。
If an object of type T is located at an address A, a pointer of type cv T* whose value is the address A is said to point to that object, regardless of how the value was obtained. [Note: For instance, the address one past the end of an array (5.7) would be considered to point to an unrelated object of the array’s element type that might be located at that address. — [n3337 § 3.9.2 p3]
在查看 §5.9 和 §5.10 之后,我认为以下内容在技术上可能是合法的,尽管它可能具有未指定的行为:
if (&mA + 1 == &mB && &mB + 1 == &mC && &mC + 1 == &mD) {
return ((int*)(this))[index];
}
甚至转换也是合法的,因为规范说标准布局 class 可以转换为指向其第一个成员的指针。 [n3337 § 9.2 p20]
不推荐这种东西,因为它不受C++标准的保证。然而,大多数编译器确实明确定义了它们的内存布局行为(通常基于每个体系结构)并提供 #pragma
s 用于操纵打包行为(例如 MSVC 中的 #pragma pack
)。如果您 understand/leverage 这些功能,您可以让它在大多数 compilers/architectures 上工作。 但是,它不是可移植的!对于每个新的编译器,您都需要重新测试和调整,这是一项代价高昂的维护任务。一般来说,我们更喜欢便携性更好。
如果你真的想这样做,你可以添加一个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++ 标准保证。
我看过下面的代码:
class SomeClass
{
public:
int mA;
int mB;
int mC;
int mD;
int operator[] (const int index) const
{
return ((int*)(this))[index];
}
};
这是如何工作的?我知道 this 关键字是指向此 class 的指针,但 属性 不知道有多少变量...我们如何安全地访问 "this" 指针的 [index]?
这 'works' 只是因为它是未定义的行为。编译器为mB
选择的内存位置恰好与&mA + 1
相同,mC
的地址为&mA + 2
和&mB + 1
等等。
然而 class 确实是 'standard layout'(所有数据成员都具有相同的访问说明符并且没有使用继承 [n3337 § 9 p7]),传统上确实会产生这种组织的成员变量。但这并不能保证。 'standard layout' 可能意味着其他含义,例如成员之间的一些填充 [n3337 § 9.2 p14],但我怀疑是否有任何平台真正做到了这一点。
规范确实说通过对象获取地址是合法的,因此计算 &mA + 1
是合法的。 [n3337 § 5.7 p5] 并且规范还说,无论指针是如何计算的,具有正确类型和值的指针都会指向一个对象。
If an object of type T is located at an address A, a pointer of type cv T* whose value is the address A is said to point to that object, regardless of how the value was obtained. [Note: For instance, the address one past the end of an array (5.7) would be considered to point to an unrelated object of the array’s element type that might be located at that address. — [n3337 § 3.9.2 p3]
在查看 §5.9 和 §5.10 之后,我认为以下内容在技术上可能是合法的,尽管它可能具有未指定的行为:
if (&mA + 1 == &mB && &mB + 1 == &mC && &mC + 1 == &mD) {
return ((int*)(this))[index];
}
甚至转换也是合法的,因为规范说标准布局 class 可以转换为指向其第一个成员的指针。 [n3337 § 9.2 p20]
不推荐这种东西,因为它不受C++标准的保证。然而,大多数编译器确实明确定义了它们的内存布局行为(通常基于每个体系结构)并提供 #pragma
s 用于操纵打包行为(例如 MSVC 中的 #pragma pack
)。如果您 understand/leverage 这些功能,您可以让它在大多数 compilers/architectures 上工作。 但是,它不是可移植的!对于每个新的编译器,您都需要重新测试和调整,这是一项代价高昂的维护任务。一般来说,我们更喜欢便携性更好。
如果你真的想这样做,你可以添加一个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++ 标准保证。