Я столкнулся со следующей проблемой и не знаю, можно ли ее решить элегантным способом:
wifi
и sntp
в main. Я не могу сделать это в глобальном пространстве, потому что они зависят от планировщика задач, который запускается только при вызове main.Итак, я снова попадаю в страну циклической зависимости. Моя первоначальная мысль бороться с этим состояла в том, чтобы просто объявить статическую переменную, но оказалось, что это не работает так, как я думал, поскольку объявление «внешнего» конфликтует с более поздним определением.
Вот код:
#include <cstdio>
#include <functional>
struct wifi_config {
std::function<void()> callback;
};
struct wifi {
wifi(const wifi_config& cfg) {}
};
struct sntp {
sntp() = default;
auto start() -> void { printf("SNTP start!\n"); }
};
int main() {
extern sntp mysntp;
static wifi mywifi(wifi_config{
.callback = [&]() -> void {
mysntp.start();
}
});
static sntp mysntp;
}
И вот ошибка:
<source>:28:17: error: 'sntp mysntp' conflicts with a previous declaration
28 | static sntp mysntp;
|
Как мне обойти это?
В приведенном выше примере sntp
не зависит от wifi
, у вас нет цикла.
Нет такой вещи, как «Вперед, объявляя статическую переменную». «Прямые объявления» предназначены для типов (структуры/класса) и их шаблонов. Чего именно вы пытаетесь достичь с точки зрения конечного пользователя?
Если это так extern
, вы говорите, что mysntp определен в другой единице компиляции. Объявляя его static
, вы объявляете его и говорите, что он доступен только в этой единице компиляции (файл cpp). Это несовместимо.
неверно, extern
может относиться к одной и той же единице перевода.
Нет, это просто передает слабый символ компоновщику на самом деле. Например, вы можете сделать это: godbolt.org/z/6dcYnY9rG
@glades это не относится к локальной статической функции.
@appleapple К сожалению
@glades не совсем так, у вас порядок внутри той же функции. глобалы - другое дело.
Это работает? (TBH, я не уверен, что это так, это зависит от того, когда вызывается обратный вызов.)
int main() {
static sntp *mysntp_ptr;
static wifi mywifi(wifi_config{
.callback = [&]() -> void {
mysntp_ptr->start();
}
});
static sntp mysntp;
mysntp_ptr = &mysntp;
}
Самый безболезненный способ - поместить их обоих как члены одной и той же структуры. Порядок объявления элементов в структуре соответствует порядку, в котором они создаются:
int main() {
struct statics {
wifi mywifi{
wifi_config{
.callback = [this]() -> void {
mysntp.start();
}
}
};
sntp mysntp;
};
static auto [ mywifi, mysntp ] = statics();
}
Это имеет немного другую семантику, если один из конструкторов должен был бросить вызов, но это не должно быть проблемой в main()
.
Кроме того, статика в main
бесполезна, потому что она будет уничтожена при выходе из main, очень похоже на то, если бы они были автоматическими переменными. Если бы вы переключились на автоматические переменные, вы могли бы просто удалить static
в static auto [ ...
.
Чтобы ответить на ваш вопрос буквально, можно «переслать объявление» статических переменных (получить ссылку на них до того, как они будут инициализированы):
int main() {
// fake "forward declaration"
constexpr auto get_mysntp = [](sntp init()){
static sntp mysntp(init());
return std::integral_constant<sntp*, &mysntp>{};
};
constexpr sntp& mysntp = *decltype(get_mysntp(nullptr))::value;
static wifi mywifi(wifi_config{
.callback = [&]() -> void {
mysntp.start();
}
});
// fake "definition" (actually construct the static variable)
get_mysntp([]{ return sntp{}; });
}
Что может быть полезно, если ваш пример более сложный. struct
статики, наверное, достаточно.
«Кроме того, статика в main бесполезна, потому что она будет уничтожена при выходе из main». Вы уверены в этом? Я думал, что смысл всей статики в том, чтобы сохранять ее при вызовах функций.
@glades main — это первая вызываемая функция и последняя оставшаяся функция, вся статика уничтожается после ее завершения.
Ах забыл объяснить. Да, в пустом cpp, но я запускаю это на микропроцессоре RTOS, который имеет app_main() вместо main(), который вызывается main(). Статика остается в силе на протяжении всей программы.
Я могу что-то упустить, но вам уже нужно, чтобы инициализация
mysntp
произошла до вызова обратного вызова (или вызовstart
определенно не определен). Так почему же его нельзя объявить сначала?