Подтвердить что ты не робот

Предотвращение создания пользователями неназванных экземпляров класса

Для многих классов RAII "guard" , созданных как анонимные переменные, не имеет никакого смысла:

{
    std::lock_guard<std::mutex>{some_mutex};
    // Does not protect the scope!
    // The unnamed instance is immediately destroyed.
}

{
    scope_guard{[]{ cleanup(); }};
    // `cleanup()` is executed immediately!
    // The unnamed instance is immediately destroyed.
}

Из в этой статье:

Анонимные переменные в С++ имеют "область видимости", то есть они уничтожаются в конце выражения, в котором они созданы.


Есть ли способ запретить пользователю создавать экземпляры без имени? ( "Предотвращение" может быть слишком сильным - "сделать это очень сложно" ).

Я могу думать о двух возможных обходных решениях, но они вводят синтаксические накладные расходы при использовании класса:

  • Скрыть класс в пространстве имен detail и предоставить макрос.

    namespace detail
    {
        class my_guard { /* ... */ };
    };
    
    #define SOME_LIB_MY_GUARD(...) \
        detail::my_guard MY_GUARD_UNIQUE_NAME(__LINE__) {__VA_ARGS__}
    

    Это работает, но хаки.

  • Разрешить пользователю пользоваться защитой через функцию более высокого порядка.

    template <typename TArgTuple, typename TF>
    decltype(auto) with_guard(TArgTuple&& guardCtorArgs, TF&& f)
    {
        make_from_tuple<detail::my_guard>(std::forward<TArgTuple>(guardCtorArgs));
        f();
    }
    

    Использование:

    with_guard(std::forward_as_tuple(some_mutex), [&]
    {
        // ...
    });
    

    Это обходное решение не работает, когда инициализация класса защиты имеет "свободный" синтаксис:

    {
        auto _ = guard_creator()
                     .some_setting(1)
                     .some_setting(2)
                     .create();
    }
    

Есть ли лучшая альтернатива? У меня есть доступ к возможностям С++ 17.

4b9b3361

Ответ 1

Единственный разумный способ, по которому я думаю, состоит в том, чтобы заставить пользователя передать результат guard_creator::create в некоторый guard_activator, который принимает значение lvalue-reference в качестве параметра.

Таким образом, пользователь класса не имеет никакого способа, кроме как создать объект с именем (разумный вариант, который будет делать большинство разработчиков), или new он затем разыменовать (безумные параметры)

например, вы сказали в комментариях, которые вы работаете над создателем асинхронной цепочки без выделения. Я могу думать о API, который выглядит так:

auto token = monad_creator().then([]{...}).then([]{...}).then([]{...}).create();
launch_async_monad(token); //gets token as Token&, the user has no way BUT create this object with a name 

Ответ 2

Если у вас есть доступ к полному потенциалу С++ 17, вы можете расширить идею использования статической функции factory во что-то полезное: гарантированное копирование позволяет сделать статическую функцию factory возможной даже для непередвижных классов, а атрибуты [[nodiscard]] побуждают компилятор выдать предупреждение, если возвращаемое значение игнорируется.

class [[nodiscard]] Guard {
  public:
    Guard(Guard& other) = delete;
    ~Guard() { /* do sth. with _ptr */ }
    static Guard create(void* ptr) { return Guard(ptr); }
  private:
    Guard(void* ptr) : _ptr(ptr) {}
    void* _ptr;
};

int main(int, char**) {
  Guard::create(nullptr);
  //auto g = Guard::create(nullptr);
}

Компилировать в компиляторе

Ответ 3

Вы можете использовать расширяемый инструмент lint, такой как Vera ++ https://bitbucket.org/verateam/vera/wiki/Home, который позволяет вам использовать свой код, вы можете создавать новые правила с помощью Python или tcl (я предпочитаю Python)

Возможный поток - после каждой фиксации ваша CI-система (например, Jenkins) будет запускать задание, которое выполняет Vera ++ и проверяет такие надзоры, после сбоя выдается сообщение коммиттеру.

Ответ 4

Канонический способ предотвратить создание экземпляра класса заключается в создании его конструктора private. Чтобы получить один из желаемых экземпляров, вы вызываете метод static, который возвращает ссылку на построенный объект.

class Me {
public:
    static Me &MakeMe() { return Me(); }
private:
    Me();
}; // Me

Это не помогает, конечно, но это, вероятно, заставит программиста остановиться!

int main() {
    Me(); // Invalid
    Me m; // Invalid
    Me::MakeMe(); // Valid - but who'd write that?
    Me m = Me::MakeMe();
} // main()

Я знаю, что это не прямой аналог экземпляров Guard, которые вы описываете, но, может быть, вы могли бы адаптировать концепцию?