在内部没有数组的情况下重载[]运算符

时间:2015-09-13 05:19:40

标签: c++ operator-overloading

我见过以下代码:

class SomeClass
{
public:
    int mA;
    int mB;
    int mC;
    int mD;

    int operator[] (const int index) const
    {
        return ((int*)(this))[index];
    }
};

这是如何工作的?我知道this关键字是指向这个类的指针,但没有知道有多少变量的属性...我们怎样才能安全地访问"这个"指针?

2 个答案:

答案 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]