Вперед, объявляя статическую переменную в C++

Я столкнулся со следующей проблемой и не знаю, можно ли ее решить элегантным способом:

  1. Мне нужно статически инициализировать интерфейс wifi и sntp в main. Я не могу сделать это в глобальном пространстве, потому что они зависят от планировщика задач, который запускается только при вызове main.
  2. Wi-Fi API принимает обратный вызов, который требует наличия интерфейса sntp для его запуска, таким образом захватывая его по ссылке.
  3. Служба sntp зависит от службы Wi-Fi, которая будет запущена, поскольку служба Wi-Fi запустит сетевой интерфейс, который нужен sntp (я знаю, что это плохой дизайн, но сейчас я как бы исправил это решение).

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

Вот код:

Демо

#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;
      |           

Как мне обойти это?

Я могу что-то упустить, но вам уже нужно, чтобы инициализация mysntp произошла до вызова обратного вызова (или вызов start определенно не определен). Так почему же его нельзя объявить сначала?

StoryTeller - Unslander Monica 11.04.2023 16:05

В приведенном выше примере sntp не зависит от wifi, у вас нет цикла.

Jarod42 11.04.2023 16:11

Нет такой вещи, как «Вперед, объявляя статическую переменную». «Прямые объявления» предназначены для типов (структуры/класса) и их шаблонов. Чего именно вы пытаетесь достичь с точки зрения конечного пользователя?

Marek R 11.04.2023 19:09
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
3
126
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Если это так extern, вы говорите, что mysntp определен в другой единице компиляции. Объявляя его static, вы объявляете его и говорите, что он доступен только в этой единице компиляции (файл cpp). Это несовместимо.

неверно, extern может относиться к одной и той же единице перевода.

apple apple 11.04.2023 16:05

Нет, это просто передает слабый символ компоновщику на самом деле. Например, вы можете сделать это: godbolt.org/z/6dcYnY9rG

glades 11.04.2023 16:05

@glades это не относится к локальной статической функции.

apple apple 11.04.2023 16:09

@appleapple К сожалению

glades 11.04.2023 16:13

@glades не совсем так, у вас порядок внутри той же функции. глобалы - другое дело.

apple apple 11.04.2023 16:14

Это работает? (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 13.04.2023 09:26

@glades main — это первая вызываемая функция и последняя оставшаяся функция, вся статика уничтожается после ее завершения.

Artyer 13.04.2023 09:32

Ах забыл объяснить. Да, в пустом cpp, но я запускаю это на микропроцессоре RTOS, который имеет app_main() вместо main(), который вызывается main(). Статика остается в силе на протяжении всей программы.

glades 13.04.2023 11:18

Другие вопросы по теме