Каков порядок потока управления при компиляции кода для класса на C ?

#c #oop #control-flow #access-specifier

#c #ооп #поток управления #спецификатор доступа

Вопрос:

Я компилирую класс, полная программа для которого приведена ниже:

 #include<iostream>
using namespace std;

class Test{
    public:
        Test()
        {
            cout<<"Test variable created...n";
            // accessing width variable in constructor
            cout<<"Width is "<<width<<".n";
        }
        void setHeight(int h)
        {
            height = h;
        }
        void printHeight()
        {
            cout<<"Height is "<<height<<" meters.n";
        }
        int width = 6;
    protected:
        int height;
};

int main()
{
    Test t = Test();
    t.setHeight(3);
    t.printHeight();
    return 0;
}
 

Код работает абсолютно нормально, но как конструктор может получить доступ к переменной width , которая не была объявлена до конца public блока. Кроме того, как функции-члены могут обращаться к переменным, объявленным позже в общедоступном блоке? Разве C не является последовательным (выполнение инструкций в порядке их написания)?

Комментарии:

1. Объявления не являются операторами. Объявления класса собираются и сохраняются перед попыткой компиляции его кода. Кроме того, операторы не обязательно должны выполняться последовательно, если наблюдаемое поведение программы будет таким же, AFAIK, в соответствии с правилом «как если бы».

2. «Разве C не является последовательным (выполнение инструкций в том порядке, в котором они написаны)?» Нет, это не так. Инструкции могут выполняться в другом порядке, например, списки инициализаторов элементов. Инструкции выполняются в том порядке, в котором члены перечислены в классе, а не в порядке фактических инструкций.

3. Объявить как int height{}; , чтобы пользователи не могли вызывать неопределенное поведение, пытаясь printHeight() , не имея setHeight() .

4. Переменные-члены инициализируются ( int width = 6; ) перед вызовом конструктора. Это означает, что структура класса должна быть известна до того, как можно будет вызвать первый нестатический метод.

5. Здесь вы можете увидеть, что происходит до того, как фактический конструктор начнет свою работу. Для этого есть лучшая документация, но я не смог их найти.

Ответ №1:

Думайте о встроенных определениях в классе как о просто синтаксическом сахаре для объявления функции, а затем определения вне класса. Выполнение этого вручную приведет к преобразованию кода в

 class Test{
    public:
        Test();
        void setHeight(int h);
        void printHeight();
        int width = 6;
    protected:
        int height;
};

Test::Test()
{
    cout<<"Test variable created...n";
    // accessing width variable in constructor
    cout<<"Width is "<<width<<".n";
}

void Test::setHeight(int h)
{
    height = h;
}

void Test::printHeight()
{
    cout<<"Height is "<<height<<" meters.n";
}
 

И вы можете видеть из этого преобразования, что член класса теперь находится «перед» определениями функций, поэтому нет причин, по которым они не могут знать о переменной.

Технический термин для этого — вызвать контекст полного класса, и суть его в том, что когда вы находитесь в теле функции-члена или в списке инициализации члена класса, класс считается завершенным и может использовать все, что определено в классе, независимо от того, где в классе это объявлено.

Комментарии:

1. Верно ли это также в списке инициализаторов конструктора?

2. @VincentFourmond Да. Список инициализации элемента считается частью тела функции

3. Спасибо @NathanOlivier, я думаю, это было бы ценной точностью в вашем ответе?

4. Добавил @VincentFourmond.

Ответ №2:

Это связано с тем, что тело функции-члена представляет собой контекст класса с полным классом, как указано в приведенных ниже утверждениях:

Из class.mem.general #6:

6. Контекст полного класса класса — это:

  • тело функции ([dcl.fct.def.general]),
  • аргумент по умолчанию,
  • noexcept-спецификатор ([except.spec]), или
  • инициализатор элемента по умолчанию

в пределах спецификации-члена класса.

Вышесказанное означает, что тело функции представляет собой контекст полного класса класса, что, в свою очередь, означает, что width здесь разрешено использование внутри конструктора и использование height внутри функции-члена setHeight и printHeight , даже если эти элементы данных появятся позже при написании определения класса.