2015-09-13 3 views
2

Я видел следующий код:Перегрузка оператора [], без массива внутри

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

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

Как это работает? Я знаю, что это ключевое слово является указателем на этот класс, но без свойства, зная, сколько переменных существует ... как мы можем безопасно получить доступ к [index] этого «указателя»?

+0

Просто по чистой случайности это работает –

+4

Это действительно не работает, это неопределенное поведение. если бы ваш класс был более сложным, он определенно дал бы вам тарабарщину –

+0

Итак, даже если пользователь хороший гражданин и использует только индекс от 0 до 3 ... он все еще не гарантирован, что индекс 0 возвращает mA? – ChaoSXDemon

ответ

4

Этот тип вещей не рекомендуется, поскольку он не гарантируется стандартом C++. Однако большинство компиляторов явно определяют свое поведение в макете памяти (часто на основе каждой архитектуры) и предоставляют #pragma с для управления поведением упаковки (например, #pragma pack в MSVC). Если вы понимаете/используете эти функции, вы можете заставить его работать с большинством данных компиляторов/архитектур. Тем не менее, это будет не быть портативным! Для каждого нового компилятора вам необходимо перепроверить и настроить дорогостоящую задачу обслуживания. Как правило, мы предпочитаем большую легкость переносимости.

Если вы действительно хотите это сделать, вы можете добавить 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++.

+0

Интересно :) Спасибо за эту удивительную альтернативу – ChaoSXDemon

+0

Пару каламбуров: 'sizeof (int)' не гарантируется '4', хотя это с достаточной долей современных компиляторов. Кроме того, если структура содержит 4 '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' По умолчанию прокладка будет (потенциально) работать только для структур, которые содержат элементы нескольких типов, поскольку требования к выравниванию различаются по типу. Выравнивание структуры будет определяться требованием согласования его первого члена с дополнением для обеспечения выравнивания всех остальных членов. – Peter

+1

@Peter Стандарт C++ позволяет добавлять дополнения в любом месте (за исключением начала). Компилятор мог бы выровнять все 4-байтные ints до 8-байтовых границ, если бы это действительно было необходимо. –

0

Это «работает» только потому, что это неопределенное поведение. Место памяти, которое выбирает компилятор для mB, совпадает с &mA + 1, адрес для mC: &mA + 2 и &mB + 1 и так далее.

Однако верно, что класс является «стандартным макетом» (все члены данных имеют один и тот же спецификатор доступа и не наследование используется [n3337 § 9 p7]), который традиционно производит эту организацию переменных-членов. Однако это не гарантировано. «стандартная компоновка» может означать что-то еще, например, некоторое количество дополнений между членами [n3337 § 9.2 p14], но я сомневаюсь, что есть какая-то платформа, которая на самом деле это делает.

В спецификации говорится, что законно получить адрес один за объектом, поэтому расчет &mA + 1 является законным. [n3337 § 5.7 p5] И в спецификации также говорится, что указатель с правильным типом и значением указывает на объект независимо от того, как вычисляется указатель.

Если объект типа T расположен по адресу A, указывается указатель типа cv T *, значение которого является адресом A, независимо от того, как это значение было получено. [Примечание. Например, адрес, расположенный за концом массива (5.7), будет считаться указывать на несвязанный объект типа элемента массива, который может быть расположен по этому адресу. — [n3337 § 3.9.2 p3]

После рассмотрения §5.9 и §5.10 я думаю, что следующий может быть технически законным, хотя она могла бы иметь неопределенное поведение:

if (&mA + 1 == &mB && &mB + 1 == &mC && &mC + 1 == &mD) { 
    return ((int*)(this))[index]; 
} 

Даже литье законно, так как спецификация говорит, что стандартный класс макета может быть добавлен к указателю на его первый член. [n3337 § 9.2 p20]

+0

Это круто, что вы проверяете адреса! – ChaoSXDemon

Смежные вопросы