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

Для многих классов 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.
}

From 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.


person Vittorio Romeo    schedule 30.11.2016    source источник
comment
Я думаю, вы спрашиваете не в том направлении. Разработчики C ++ должны понимать, почему lock_guard существует и почему он должен оставаться в живых, пока сохраняется область, которую он защищает. попытки добиться этого через API или внутреннюю реализацию просто избыточны.   -  person David Haim    schedule 30.11.2016
comment
@Danh: ответ на другой вопрос - это мой обходной путь макроса. Не могли бы вы снова открыть этот вопрос, если я сделаю более очевидным, что ищу решение, не основанное на макросах? @David: я согласен с примером lock_guard. Я работал над генерацией некоторой асинхронной цепочки без выделения памяти с плавным синтаксисом, где очень естественно иметь анонимные цепочки. Но имя все равно нужно, иначе хранилище для цепочки умирает слишком быстро. Это не так очевидно, как lock_guard, и я бы хотел предотвратить эту ошибку.   -  person Vittorio Romeo    schedule 30.11.2016
comment
@VittorioRomeo На этот вопрос есть и другие ответы. И, ИМХО, лучше, если мы сможем централизовать всю дискуссию по этому поводу в одном месте. В любом случае, этот ответ выглядит очень интересно   -  person Danh    schedule 30.11.2016
comment
@VittorioRomeo, значит, вы работаете над версией std::async + future::then с выделенным стеком?   -  person David Haim    schedule 30.11.2016
comment
Это также связано с этим вопросом: http://stackoverflow.com/questions/914861/disallowing-creation-of-the-Contemporary-objects   -  person Danh    schedule 30.11.2016
comment
создание экземпляров как анонимных переменных вообще не имеет смысла, кроме случаев, когда результат привязан к ссылке и продлен на время жизни. Это единственный способ написать make_lock_guard до C ++ 17. Одна из возможностей - принудительно использовать функцию make_, а затем пометить ее [[nodiscard]].   -  person T.C.    schedule 30.11.2016
comment
Не отвечает на этот вопрос, но в clang-tidy есть проверка, которая ищет этот шаблон, который уже помогает.   -  person etarion    schedule 30.11.2016


Ответы (4)


Единственный разумный способ, о котором я думаю, - это заставить пользователя передать результат 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 
person David Haim    schedule 30.11.2016
comment
MSVC ++ передает ей привет - person Danh; 30.11.2016
comment
@Danh они исправили этот глюк давным-давно - person David Haim; 30.11.2016
comment
rextester.com/DALS87730 успешно скомпилирован с /Za и без него. 1900 означает, что это VS2015 - person Danh; 30.11.2016
comment
@Danh Вы можете скомпилировать с / we4239, чтобы рассматривать это расширение как ошибку - person Serikov; 30.11.2016
comment
Это выглядит очень многообещающим для моей конкретной ситуации. Я попробую как можно скорее - спасибо - person Vittorio Romeo; 30.11.2016
comment
Если вы измените его на rvalue-ссылку, разве это не сработает? Но позвольте вам вызвать функцию, в которой объект-охранник активен во время вызова функции. Это противоречит требованию my в вопросе (это безымянный объект), но охрана по-прежнему активна, когда это необходимо. - person Hans Olsson; 30.11.2016
comment
Ссылка @HansOlsson rvalue может уловить временные затраты. - person David Haim; 30.11.2016
comment
Да, временно могут поймать временно. Но вызывает ли это конкретную проблему? Он живет во время вызова функции. Однако я понимаю, что если вы хотите вызвать две функции и хотите, чтобы блокировка была активна для обеих, это не сработает - и по этой причине lvalue-reference может быть лучше. Пользователь может сделать ту же ошибку, имея две отдельные области с защитой и вызовом функции; но это кажется странным. - person Hans Olsson; 30.11.2016

Если у вас есть доступ к полному потенциалу 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);
}

Компиляция в обозревателе компиляторов

person kubanrob    schedule 30.11.2016

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

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

person Max Raskin    schedule 30.11.2016

Канонический способ предотвратить создание экземпляра класса - создать его конструктор 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 экземпляров, которые вы описываете, но, может быть, вы могли бы адаптировать концепцию?

person John Burger    schedule 30.11.2016
comment
Я не думаю, что это нужно даже компилировать; вы привязываете rvalue к ссылке lvalue. - person Anton Golov; 30.11.2016