【问题标题】:prohibiting instantiation as a temporary object (C++)禁止实例化为临时对象 (C++)
【发布时间】:2011-06-03 02:29:56
【问题描述】:

我喜欢在 c++ 中使用哨兵类,但我似乎有一种精神上的痛苦,导致反复编写如下错误:

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

不用说,这会失败,因为哨兵在创建后立即死亡,而不是按预期在作用域结束时死亡。有什么方法可以防止 MySentryClass 被实例化为临时的,从而使上述代码要么无法编译,要么至少在运行时中止并显示错误消息?

【问题讨论】:

  • 我不认为回答者到目前为止理解 - 你知道如何正确地做到这一点,但发现自己经常犯这个错误,你想知道是否有自动检测它的方法。对吗?
  • @Mark, @DeadMG:是的,没错。

标签: c++ temporary temporary-objects


【解决方案1】:

我认为#define 是最好的方法。
但就像不使用 #define 的一个选项:

主要

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

【讨论】:

    【解决方案2】:

    不确定你会喜欢这个解决方案,但解决方案很可能是grep

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

    您可以做的另一件事是使用sedperl 预处理您的源文件,将MySentryClass( 替换为\n#error MySentryClass used incorrectly\n,希望这会给您一个接近错误位置的行号。如何执行此操作取决于您的构建系统。

    【讨论】:

      【解决方案3】:

      您尝试做的事情在 C++ 中是完全合法的,我认为没有办法禁止它。

      【讨论】:

        【解决方案4】:

        我想不出一种自动检测您是否犯此错误的方法。您总是可以创建一个扩展为正确事物的宏,并在您继续使用错误时使用它来声明哨兵。

        #define MY_SENTRY_CLASS(_X) MySentryClass _sentry(_X)
        

        然后使用

        MY_SENTRY_CLASS(arg);
        

        或者在你的显示器上贴一个便利贴来提醒你。

        【讨论】:

        • 我发现如果你构建一个宏来为你处理块范围,这会更好。这样,您可以编写类似“guarded_block(myBlock) { ... }”的内容,它会自动运行。
        • 哦,不!不是宏,不是那些臭的东西!
        • 不要这样使用下划线。总有一天编译器之神会扇你一巴掌。
        • @Gene: 在某些情况下宏合适的,这是一个很好的例子
        • +1,如果你将__COUNTER__添加到宏中,同样可以有多个哨兵在同一个范围内。
        【解决方案5】:

        不,这个问题没有出口。要在堆栈上创建对象,你必须有公共构造函数,如果你有公共构造函数,你可能会犯你报告的错误。

        【讨论】:

          【解决方案6】:

          您唯一能做的就是将构造函数设为私有并通过辅助函数强制访问。这与初始构造语法远不相似,并且不太可能出错。您也可以在堆上分配(仍然是浪费),但更容易发现。然而,如果你希望你的类是可构造的,你不能阻止人们构造这种类型的右值。

          编辑:如果你知道 MySentryClass 总是接受一个参数,你可以禁止构造 AND 并且只允许 operator=(arguments)。这会迫使你这样做

          MySentryClass x;
          x = arg;
          

          你可以为它做一些方法链。

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

          【讨论】:

          • 这没有用,因为整个想法是使用范围来获取“在块入口调用ctor,在出口调用dtor”。
          • 但是,这并不能阻止或禁止任何事情,或者是吗?
          • 它所做的只是强制使用不太相似的语法,从而减少出错的可能性。从根本上说,该语言没有强制堆栈分配。
          猜你喜欢
          • 2019-01-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多