Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
 
 
Рейтинг 4.56/9: Рейтинг темы: голосов - 9, средняя оценка - 4.56
3 / 4 / 4
Регистрация: 22.05.2015
Сообщений: 118
1

Порядок вызова конструкторов

23.04.2017, 10:04. Показов 1854. Ответов 27
Метки нет (Все метки)

Всем доброго дня.
Наткнулся в коде на интересные грабли:
test.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
#include "test.h"
 
Test test;
 
Test::Test()
{
    count = 0;
}
 
MyClass::MyClass()
{
    test.count++;
}
test.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
struct Test
{
    byte count;
    Test();
};
 
class MyClass
{
    MyClass();
}
 
extern Test test;
main.cpp
C++
1
2
3
4
5
6
7
8
#include "test.h"
 
MyClass cl1, cl2;
 
int main()
{
    while(true);
}
После создания cl1 и cl2 test.count == 0;
В коде я вижу, что сначала отрабатывают конструкторы классов, а потом конструктор структуры. Почему так происходит?
Разве не должны выполняться конструкторы используемых объектов, а потом тех, что используют их.
Можно ли указать приоритет выполнения конструкторов вручную?

Пишу на System Workbench for STM32 (сборка eclipse)

Заранее спасибо за помощь
0
Лучшие ответы (1)
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
23.04.2017, 10:04
Ответы с готовыми решениями:

Порядок вызова конструкторов
Есть классы First и Second. Класс Second наследуется от First. Я имею ввиду: class Second:...

Порядок вызова конструкторов
на срр-reference нашёл тему про виртуальный деструктор, но я так и не понял (да там и не...

Порядок вызова конструкторов/деструкторов
Вопрос чисто теоретический. Попробую сформулировать, не ругайте если получится коряво. Например,...

Классы, наследование, порядок вызова конструкторов
допустим у меня эсть два класса class a { publc: char *n; a() { n= new char ; } ~a()

27
Заблокирован
23.04.2017, 10:16 2
Цитата Сообщение от beam Посмотреть сообщение
Почему так происходит?
Потому что test - это отдельная переменная в области данных, которая не является членом твоего класса и, считай, с ним не связана.
0
Don't worry, be happy
17164 / 10048 / 1934
Регистрация: 27.09.2012
Сообщений: 25,035
Записей в блоге: 1
23.04.2017, 10:16 3
Порядок инициализации объектов в разных единицах трансляции не определен.
0
Заблокирован
23.04.2017, 10:17 4
Цитата Сообщение от beam Посмотреть сообщение
Разве не должны выполняться конструкторы используемых объектов, а потом тех, что используют их.
Нет. Кто кого использует значения не имеет.
0
3 / 4 / 4
Регистрация: 22.05.2015
Сообщений: 118
23.04.2017, 10:19  [ТС] 5
В последствии планируется вынести test в закрытую библиотеку,а экземпляры классов определять в сторонних файлах. Конструктор test должен иметь приоритет выше. Есть ли способы указать компилятору, что его инициализировать нужно в первую очередь?
Я пробовал делать так:
C++
1
2
3
4
struct Test
{
byte count = 0;
};
результат тот же

ps. Кстати в отличии от eclipse Keil делает инициализацию чуть чуть подругому - при первом обращении к переменной он ее заполянет ее начальным значением и там таких проблем не возникало. К сожалению были вынуждены перейти на эклипс (кеил стоит ~6к толи $, толи евро), что не по карману и вот таким сюрпризом он нас встретил
0
С чаем беда...
Эксперт CЭксперт С++
9127 / 4646 / 1267
Регистрация: 18.10.2014
Сообщений: 10,481
23.04.2017, 10:51 6
Цитата Сообщение от beam Посмотреть сообщение
Разве не должны выполняться конструкторы используемых объектов, а потом тех, что используют их.
Спецификация языка говорит, что при вызове функции из некоей единицы трансляции или при обращении к объекту из некоей единицы трансляции все нелокальные статические объекты, определенные в этой единице трансляции, должны быть инициализированы к моменту начала ее работы.

Однако это правило не распространяется на вызовы функций и обращения к объектам, сделанные в процессе инициализации какого-то другого объекта. То есть вызов конструктора MyClass::MyClass(), который делается в процессе инициализации объекта cl1, не приводит к тому, что компилятор прерывает инициализацию cl1 и бросается инициализировать test.

Цитата Сообщение от beam Посмотреть сообщение
Разве не должны выполняться конструкторы используемых объектов, а потом тех, что используют их.
В общем случае это невозможно. И, еще раз, в общем случае не делается прерывания процесса инициализации одного объекта для того, чтобы инициализировать другой.

Цитата Сообщение от beam Посмотреть сообщение
Можно ли указать приоритет выполнения конструкторов вручную?
Для статических объектов между разными единицами трансляции - нет. Тут может помочь только "ручная" реализация создания объектов.
0
3 / 4 / 4
Регистрация: 22.05.2015
Сообщений: 118
23.04.2017, 10:57  [ТС] 7
Что значит ручная?
Ведь любая ручная (если я правильно понял - это самостоятельный вызов какой-то функции конструктора) функция будет выполнениа уже после работы конструкторов класса
0
С чаем беда...
Эксперт CЭксперт С++
9127 / 4646 / 1267
Регистрация: 18.10.2014
Сообщений: 10,481
23.04.2017, 11:08 8
Цитата Сообщение от beam Посмотреть сообщение
Что значит ручная?
Ну в самой грубой форме (не заботясь о выравнивании, многопоточности и т.п.) что-то вроде

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
static unsigned char raw_test[sizeof(Test)];
static bool test_initialized;
 
Test &test()
{
  if (!test_initialized)
  {
    new (raw_test) Test;
    test_initialized = true;
  }
  
  return reinterpret_cast<Test &>(raw_test);
}
и в дальнейшем работать через test(). (В облагороженной форме - синглтон.)

Вызывая test() мы заставляем компилятор делать то, что он не хотел делать - бросать все и инициализировать объект типа Test.
0
Don't worry, be happy
17164 / 10048 / 1934
Регистрация: 27.09.2012
Сообщений: 25,035
Записей в блоге: 1
23.04.2017, 11:23 9
TheCalligrapher, а почему не
C++
1
2
3
4
5
Test &test()
{
  static Test test;
  return test;
}
???
или почему не как в gcc - ссылки типа istream &cin;
А в функции инициализации - placement new для всех объектов в нужном порядке,
хотя здесь еще нужно позаботится о вызове этой функции.
0
С чаем беда...
Эксперт CЭксперт С++
9127 / 4646 / 1267
Регистрация: 18.10.2014
Сообщений: 10,481
23.04.2017, 11:27 10
Цитата Сообщение от Croessmah Посмотреть сообщение
а почему не
Потому что я хотел постараться максимально соблюсти низкоуровневую семантику исходного варианта.

Цитата Сообщение от Croessmah Посмотреть сообщение
или почему не как в gcc - ссылки типа std::istream &cin;
Да, хорошая идея. Я только не уверен был, гарантируется ли для ссылок статическая инициализация. Скорее всего да.
1
Don't worry, be happy
17164 / 10048 / 1934
Регистрация: 27.09.2012
Сообщений: 25,035
Записей в блоге: 1
23.04.2017, 11:31 11
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Да, хорошая идея.
Только про инициализацию я забыл.
Без инициализации упремся в ошибку компиляции.
0
3 / 4 / 4
Регистрация: 22.05.2015
Сообщений: 118
23.04.2017, 11:59  [ТС] 12
Цитата Сообщение от Croessmah Посмотреть сообщение
Test &test()
{
static Test test;
return test;
}
А разве мы тут не получим теже грабли? Ведь конструктор может вызвать эту функцию, а она вернет ссылку на ячейку памяти, которая еще не инициализирована
0
nd2
3416 / 2796 / 1251
Регистрация: 29.01.2016
Сообщений: 9,426
23.04.2017, 12:20 13
Цитата Сообщение от beam Посмотреть сообщение
А разве мы тут не получим теже грабли?
Нет. Это вместо глобальной переменной. А так в конструкторе MyClass:
C++
1
2
3
4
5
MyClass::MyClass()
{
    test().count++; // при первом вызове test() будет создан статический объект Test, 
                           // с инициализированной 0 count
}
0
3 / 4 / 4
Регистрация: 22.05.2015
Сообщений: 118
23.04.2017, 12:35  [ТС] 14
ps. странно, попробовал - закомпилилось, только добавилось 50кб кода, что есть пятая часть памяти мк и вообще не понятно откуда она взялась

Добавлено через 8 минут
Сработало!
Сделал инициализацию не конструктором и ене в самой структуре, а при создании статик объекта и места стал занимать разумное количество

C++
1
2
3
4
5
6
7
8
9
10
Test &test()
{
    static Test test =
    {
        0,0,0,0,0,0,0,0,0,0,// links
        0,0,0,0,0,0,0,//busy ID's
        0// counter
    };
    return test;
}
Спасибо большое!
0
3 / 4 / 4
Регистрация: 22.05.2015
Сообщений: 118
07.02.2018, 17:38  [ТС] 15
В процессе...

Добавлено через 33 минуты
Появилась аналогичная проблема, которую на данный момент нет возможности решить через static член (эклипс в этом случае вставляет либы операторов new, которые я не использую в проекте и добавляет ~80кб кода, что меня не устраивает).

Узнал про способ со статическим member'ом с конструктором-инициализатором.

В общем виде записывается так:

SPI.h
C++
1
2
3
4
5
6
7
8
9
10
class SPI
{
    class Initializer
    {
        Initializer();
    } static const _init;
public:
    SPI(){};
    void foo();
};

SPI.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
#include "SPI.h"
 
SPI::Initializer::Initializer()
{
    ...
}
 
 
SPI::foo()
{
    ...
}

Hardware.h
C++
1
2
3
4
5
6
7
#include "SPI.h"
 
class Hardware
{
public:
    static SPI spi;
};
Hardware.cpp
C++
1
2
3
#include "Hardware.h"
 
SPI Hardware::spi;

Device.h
C++
1
2
3
4
5
6
7
#include "Hardware.h"
 
class Device : public Hardware
{
public:
    Device ();
};

Device.cpp
C++
1
2
3
4
5
6
#include "Device .h"
 
Device::Device()
{
    Hardware::spi.foo();
}
Класс SPI описывает периферию. Методы класса SPI могут быть использованы только после конструктора (если зайти до него, то просто не выходят из while loop). Т.к. порядок вызова конструкторов не определен, то у меня сначала вызывается конструктор Device и все зависает в foo(). Я создал этот static member Initializer в надежде, что он будет вызван первым, т.к. является статик членом класса, но он в этот конструктор вообще не заходит. Судя по всему - просто оптимизирует. Попытка сделать volatile static const не помогла.

Что тут можно придумать?

Может есть более красивый вариант решения такой проблемы? Все голову об него уже поломал, т.к. таких классов периферии несколько и каждый требует первоначальной инициализации. Привычный подход через вызов некой init() из main мне не очень нравится, т.к. планируется все исходники завернуть в либу с чистым main без дополнительных вызовов.

Заранее спасибо!
0
зомбяк
1532 / 1177 / 332
Регистрация: 14.05.2017
Сообщений: 3,822
07.02.2018, 18:35 16
Цитата Сообщение от beam Посмотреть сообщение
SPI.cpp
А где объявление SPI::_init ?

Нужно ж что-то такое:

C++
1
const SPI::Initializer SPI::_init;
Добавлено через 2 минуты
Правда пришлось конструктор Initializer() сделать публичным, но так вроде бы работает

http://cpp.sh/3ybg2

Добавлено через 1 минуту
Или сделать так
C++
1
2
3
4
5
6
7
8
9
10
11
class SPI
{
    class Initializer
    {
        Initializer();
        friend class SPI;
    } static const _init;
public:
    SPI(){};
    void foo();
};
Добавлено через 20 минут
А вот старая 2010 студия такое не переваривает, ей нужно в таком виде:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class SPI
{
    class Initializer
    {
        Initializer();
        friend class SPI;
    };
    static const Initializer _init;
public:
    SPI(){};
    void foo();
};
 
const SPI::Initializer SPI::_init;
 
SPI::Initializer::Initializer()
{
}
 
void SPI::foo()
{
}
1
3 / 4 / 4
Регистрация: 22.05.2015
Сообщений: 118
08.02.2018, 08:02  [ТС] 17
Слона то я и не заметил. Это я про
C++
1
const SPI2t::Initializer SPI2t::_init;
Но воз и ныне там - _init создается после экземпляра Device (он глобальный в другом .cpp) и его конструктор стартует первым. Соответственно периферия инициализируется уже после обращения к ней
0
15100 / 8104 / 1955
Регистрация: 30.01.2014
Сообщений: 13,764
08.02.2018, 08:45 18
Цитата Сообщение от beam Посмотреть сообщение
на данный момент нет возможности решить через static член (эклипс в этом случае вставляет либы операторов new, которые я не использую в проекте и добавляет ~80кб кода, что меня не устраивает).
Если оставаться на этом варианте, т.е. продолжать использовать глобальные переменные, то нужно тщательно разобраться почему там 80кб кода и откуда там new (его быть не должно).

Или же отказаться от глобальных переменных вообще, переделать архитектуру проекта.

Добавлено через 4 минуты
В случае, если самостоятельно найти причину не получается, то я предлагаю сделать минимальный рабочий пример, на котором воспроизводятся эти ~80кб кода и вызовы new. Потом это выложить сюда и мы попробуем определить в чем там ошибка.

Добавлено через 1 минуту
Я еще раз повторю, что продолжать тыкаться в разные варианты без тщательного понимания уже сложившихся проблем - это ошибочный путь. Сначала надо понять почему не получается сделать нормально, а потом уже пытаться применять костыли.
1
3 / 4 / 4
Регистрация: 22.05.2015
Сообщений: 118
08.02.2018, 09:32  [ТС] 19
=) в принципе я согласен.

Так называемый static член - это отложенная инициализация (основа синглтона). В принципе меня устраивает этот вариант. Но при попытке использовать даже простой пример у меня добавляется 52 136 байт кода.
C++
1
2
3
4
5
6
7
8
9
10
11
12
class TestClass
{
public:
    uint z;
    TestClass(){};
};
 
TestClass &guf()
{
    static TestClass as;
    return as;
}
Если я просто в проекте добавлю
C++
1
byte *bas = new byte;
, то у меня добавится 52 344 байта кода.

При использовании обоих вариантов добавляется 52 344 байта кода. Из чего я делаю вывод, что отложенная инициализация использует оператор new. Честно говоря, память мк еще не заполнена и не планирует быть заполненной в ближайшее время, но мой перфекционизм жмет на эти 50кб. Кстати это я пишу на STM32 System Workbench, а кейл в свою очередь добавляет на оператор new всего 800 байт, что по божески. Я думаю где-то в оптимизации проблемы, т.к. тот же gcc компилятор на VS с плагином Visual GDB добавляет 1кб, что тоже норм. Я всяко разно игрался с -Ox и на GCC и на G++ компиляторе - особой разницы нету.

Отказаться от глобальных не совсем получится. Есть один глобальный (и единственный) экземпляр класса (Device), использующий периферию. Он системный и есть всегда. Его наличие обязательно и он должен быть виден отовсюду.
В процессе реализации проектов на платформе будут добавляться другие глобальные экземпляры класса Device, которые тоже используют периферию. Некоторые экземпляры Device вызываются раньше static экземпляров классов периферии и, соответственно, не работают.

Если в итоге красивого решения кроме отложенного не найдется, то придется использовать его. Ну а в идеале перейти на другую среду. Тот же VS с плагином вполне рабочая версия.
0
15100 / 8104 / 1955
Регистрация: 30.01.2014
Сообщений: 13,764
08.02.2018, 11:53 20
Цитата Сообщение от beam Посмотреть сообщение
Но при попытке использовать даже простой пример у меня добавляется 52 136 байт кода.
Это странно, но в принципе возможно. Хотелось бы, конечно, посмотреть как настроена среда, как организован код.
Я все-таки еще раз спрошу: можно сюда прикрепить законченный пример, воспроизводящий эту проблему? Путь это будет, допустим, проект для STM32 System Workbench - я приду вечером с работы и запущу его, чтобы убедиться, что ни в настройках среды, ни в коде нет каких-то причин, влияющих на такое раздувание бинарника.

Цитата Сообщение от beam Посмотреть сообщение
Отказаться от глобальных не совсем получится. Есть один глобальный (и единственный) экземпляр класса (Device), использующий периферию. Он системный и есть всегда. Его наличие обязательно и он должен быть виден отовсюду.
В процессе реализации проектов на платформе будут добавляться другие глобальные экземпляры класса Device, которые тоже используют периферию. Некоторые экземпляры Device вызываются раньше static экземпляров классов периферии и, соответственно, не работают.
Получится. Можно строить свой проект иерархически - классы, объекты которых которых живут больше, включают в себя объекты тех, которые живут меньше. Если тем маложивущим объектам нужны данные из долгоживущих, то их можно передать по сылке в соответствующие рабочие методы.
Да, тут надо будет чуть больше подумать, чем с глобальными переменными, но это возможно.

_____
Вообще, если абстрагироваться, то код, который зависит от порядка инициализации глобальных переменных не очень хороший. Это уже большой повод подумать о том, что что-то не так в архитектуре проекта. Но я не собираюсь учить программировать или заставлять все переделывать, просто обращаю внимание, хотя бы на будущее. Пусть будет так, как есть, лечить будем по симптомам - глобальные переменные, так глобальные. Просто нужен материал для исследования.
1
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
08.02.2018, 11:53

Помощь в написании контрольных, курсовых и дипломных работ здесь.

Порядок вызова конструкторов при множественном наследовании
Здравствуйте, меня интересует вопрос, как изменить последовательность вызова конструкторов базовых...

Порядок вызова конструкторов при присваивании объектов одного класса
Имеется код ниже. Wein dres = rom; Где dres и rom объекты класса Wein. Класс Wein имеет...

Странный порядок вызова конструкторов и передача временного обьекта в функцию в качестве неконстантной ссылки
Есть код //g++ 5.4.0 #include &lt;iostream&gt; struct foo { foo(int){std::cout &lt;&lt; &quot;int...

Очерёдность вызова конструкторов класса
У меня есть 2 класса, к примеру Base и Mod. Mod является наследником Base. Классу Mod...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2021, vBulletin Solutions, Inc.