2016-11-30 10 views
17

Для многого RAII «охранник» классов, будучи инстанцированы как анонимные переменные не имеет смысла вообще:Предотвращение пользователей от создания неназванных экземпляров класса

{ 
    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. 
} 

От this article :

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


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

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

  1. Скрыть класс в detail имен и обеспечивает макрос.

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

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

  2. Только разрешите пользователю использовать предохранитель через функцию более высокого порядка.

    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(); 
    } 
    

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

+1

Я думаю, что вы просите в неправильном направлении. Разработчики C++ должны понимать, почему существует 'lock_guard', и почему он должен оставаться в живых, пока область, которую он защищает, стоит. попытка принудительного применения этого через API или внутренняя реализация просто избыточна. –

+2

@ Danh: ответ другого вопроса - это мой «макрос» обходной путь. Не могли бы вы рассмотреть вопрос о повторном открытии этого вопроса, если я сделаю более очевидным, что я ищу решение на основе макросов? @ Давид: для примера «lock_guard» я согласен. Я работал над некоторой цепочкой генерации async без выделения с «беглым» синтаксисом, хотя, где очень естественно иметь анонимные цепочки. Но вы все равно нуждаетесь в имени, иначе хранилище для сети быстро погибает. Это не так очевидно, как «lock_guard», и я бы хотел предотвратить эту ошибку. –

+1

@VittorioRomeo В этом вопросе есть и другие ответы. И, ИМХО, если мы можем централизовать все дискуссии об этом в одном месте, это лучше. Во всяком случае, [этот ответ] (http://stackoverflow.com/a/16192104/4115625) выглядит очень интересно – Danh

ответ

5

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

таким образом, пользователь класса не имеет никакого выхода, кроме как создать объект с именем (в здравом уме вариант, что большинство разработчиков будет делать), или 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 
+3

MSVC++ отправляет ее привет – Danh

+0

@ Danh они исправили этот глюк давным-давно –

+0

http://rextester.com/DALS87730 скомпилирован успешно и без '/ Za'. 1900 указывает, что это VS2015 – Danh

0

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

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

0

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

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

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

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

+2

Я не думаю, что это должно даже скомпилировать; вы привязываете rvalue к ссылке lvalue. –

3

Если у вас есть доступ ко всему потенциалу C++ 17, вы можете расширить идею использования статической фабричной функции во что-то полезное: гарантированное копирование позволяет сделать статическую фабричную функцию даже для непередвижных классов, а Атрибуты [[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); 
} 

Compile in Compiler Explorer