запрещение создания экземпляра как временного объекта (C++)

Мне нравится использовать классы sentry в C++, но у меня, похоже, есть психическое расстройство, которое приводит к многократному написанию ошибок, таких как:

{
  MySentryClass(arg);
  // ... other code
}

Излишне говорить, что это не потому, что турель умирает сразу после создания, а не в конце области, как и предполагалось. Есть ли способ предотвратить создание экземпляра MySentryClass как временного, так что приведенный выше код либо не компилируется, либо, по крайней мере, прерывается с сообщением об ошибке во время выполнения?

6 ответов


Я не могу придумать автоматический способ определить, делаете ли вы эту ошибку или нет. Вы всегда можете создать макрос, который расширяется до правильной вещи и использовать его для объявления sentry, если вы продолжаете использовать его неправильно.

#define MY_SENTRY_CLASS(_X) MySentryClass _sentry(_X)

и затем использовать

MY_SENTRY_CLASS(arg);

или поместите сообщение на монитор, чтобы напомнить вам.


единственное, что вы можете сделать, это сделать конструкторы частными и принудительный доступ через вспомогательную функцию. Это гораздо менее похоже на синтаксис начальной конструкции и менее вероятно, что будет ошибочным. Вы также можете выделить на куче (все еще отходы), но это намного проще обнаружить. Однако, если вы хотите, чтобы ваш класс был конструктивным, вы не можете остановить людей, создающих rvalues этого типа.

Edit: если вы знаете, что MySentryClass всегда принимает аргумент, вы можете запретить построение и только разрешить operator=(аргументы). Это заставило бы вас сделать

MySentryClass x;
x = arg;

Вы можете сделать для него какую-то цепочку методов.

MySentryClass x;
x.SetArg1(arg).SetArg2(arg2).construct();

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


не уверен, что вам понравится это решение, но решение вполне может быть grep:

find /path/to/project -type f -name \*.cpp -print0 | xargs grep -0 'MySentryClass('

еще одна вещь, которую вы могли бы сделать, это использовать sed или perl для предварительной обработки исходного файла, заменив MySentryClass( С \n#error MySentryClass used incorrectly\n, который, надеюсь, даст вам номер строки, близкий к тому, где ошибка. Как это сделать, зависит от вашей системы сборки.


Я думаю, что #define-лучший метод.
Но как вариант не использовать #define:

Main

int main()
{
    try
    {
        S arg1;
        // This will not compile
        // MySentry    x1   = MySentry::CreateSentry(arg1);

        S arg3;
        MySentry    x2(MySentry::CreateSentry(arg3));


        S arg2;
        // This will not compile
        // MySentry(arg2);

        S arg4;
        // This will generate a runtime exception
        // It will never call start() or end()
        //MySentry::CreateSentry(arg4);
    }
    catch(std::exception const& e)
    {
        std::cout << "Exception : " << e.what() << "\n";
    }
}

редактировать. Теперь работает лучше.

#include <stdexcept>
#include <iostream>

class S
{
    public:
        void start()    {std::cout << "Start\n";}
        void end()      {std::cout << "End\n";}
};

class MySentry
{
        struct Init
        {
            Init(S& s) : arg(s),bad(true) {}
           ~Init()  {if (bad) {throw std::runtime_error("Bad usage of MySentry");}}
            S&              arg;
            mutable bool    bad;
        };
    public:
        static Init CreateSentry(S& arg) { return Init(arg);}

        explicit MySentry(Init const& arg)
            : obj(arg.arg)
            , bad(false)
        {
            arg.bad = false;
            std::cout << "Created\n";
            obj.start();
        }
        MySentry(MySentry const& rhs)
            : obj(rhs.obj)
            , bad(false)
        {
            std::cout << "Copied (this may not appear)\n";
            std::cout << "If the optimizer kicks in then the copy may be elided.\n";

            // But if it did not optimize out then
            // We have to mark the temporaty as bad
            // And not call end() in its destructor.

            // Note: Never call start() here as it will always be called in the
            //       main private constrctor above
            rhs.bad = true;
        }
        ~MySentry()
        {
            if (!bad)
            {
                // Everything working
                obj.end();
            }
            std::cout << "Destroyed\n";
        }
    private:
        S&              obj;
        mutable bool    bad;
};

то, что вы пытаетесь сделать, совершенно законно в C++, и я не думаю, что есть способ запретить его.