【问题标题】:Preventing early object destruction防止早期对象破坏
【发布时间】:2016-09-08 20:35:27
【问题描述】:

在 C++ 中,如果我写的话

token make_token() { return token{}; }

然后按如下方式使用

void use_token()
{
  make_token();
  // extra code
}

在没有为变量分配标记的情况下,token 的析构函数会在额外代码执行之前触发。如何让析构函数只在函数结束时触发而不必创建变量?

注意:我想完全避免制作变量。我知道我可以执行 auto& t = make_token() 或类似操作,但我想通过返回没有立即触发析构函数的 something(我不知道是什么)来避免这种情况。

为什么我想要这个: 基本上,在我的应用程序(编程语言的编译器)中,我有这些称为令牌的东西。令牌的构造函数可以放置 { 并缩进,然后其析构函数可以放置 } 并取消缩进。我认为设置按值返回这些标记的函数是个好主意,但我实际上不想将它们分配给任何值,因为标记是无用的并且没有任何功能。

为了减轻混淆,我的token 不是词法标记。我使用工作token 代替工作cookie。它意味着在构造函数中做一些事情,等到它的作用域结束,然后在它的析构函数中做一些事情。就是这样。顺便说一句,如果我用 C# 写这个,我会写类似的东西

 using (make_token())
 {
   // my code here
 }

它会按预期工作。但事实证明,如此简单的事情在 C++ 中却是困难的。

【问题讨论】:

  • 我不认为你可以。
  • 将返回值附加到标识符是告诉编译器不要立即销毁该值的方法。
  • 我认为一个更好的问题是,你为什么不想将它分配给一个变量?如果您担心它会浪费,请不要担心。编译器很聪明,它们永远不会生成您编写的代码。只需将其分配给变量或 const ref 并且不再使用它就足以告诉编译器“我不会使用它,但析构函数不应该运行,直到它超出范围。”
  • 你的推理没有任何意义。
  • 如果在 C++ 中很难,那只是因为你拒绝使用标准的方法。 RAII 是 C++ 的自动资源清理机制,using 是 C# 的。你在这里拒绝使用 RAII,所以留下了......尴尬的黑客攻击。因为当 RAII 运行良好时,没有人取代它。

标签: c++ variables destructor object-lifetime


【解决方案1】:

是的。您可以使用常量引用。这在 C++ 中被称为最重要的 const,它是一个鲜为人知的特性。

你是这样做的:

void use_token()
{
  const token& myToken = make_token();
  // now myToken is alive until the end of this function.
}

但您必须严格按值返回才能使其正常工作(您在提供的代码中执行此操作)。

不相信的人,请在攻击帖子之前自己尝试一下。

【讨论】:

  • 嗯,我想明确避免将标记分配给变量。
  • @TheQuantumPhysicist 你显然没去过拉斯维加斯
  • @melpomene 不,它不会使 API 变得更容易,它会让人难以理解和不明显
  • 我同意@Slava,你可以做一些宏魔法来创建你想要的API,这将在后台创建变量,但是它会给代码添加副作用,这些副作用并不明显并且不直观。花费额外的时间来弄清楚这一点将抵消您的 API 通过使用更少的输入所带来的任何节省。
  • @melpomene 如果你不懂 C++,那么不要用这种语言编写 API。但是,如果你为这种语言编写 API 确实是为了创建像这样的“智能”愚蠢的解决方案,这对于 C++ 开发人员来说是不明显的,只会让事情变得复杂,而不是为了展示你的“聪明”。
【解决方案2】:

像这样:

make_token(),
[](){ /* extra stuff */ }();

确保你事后洗手:)

【讨论】:

  • 它的名字是“末日拉姆达”
  • 哇,这太可怕了。你能解释一下为什么它有效吗?
  • 一个普通的两个字符较短的版本将是:make_token(), []{ /* extra stuff */ }(); ...:-)
  • @DmitriNesteruk 它将其余代码移动到一个立即调用的临时函数中,作为调用同一表达式make_token 的一部分。返回的对象仅在结束时被销毁充分表达。使用这个技巧,我们将表达式的结尾也安排为周围函数的结尾。
  • 虽然这是一种有趣的方法,但我有太多地方需要这样做,包括嵌套的地方。所以这个解决方案不适合我,而是因为纯粹的聪明才被赞成。
【解决方案3】:

如果您能够使用 C++11 或更高版本,您可以编写类似以下的模板函数:

template <typename T, typename Functor>
void keep_alive(T&&, Functor f) {
    f();
}

...
void use_token() {
    keep_alive(make_token(), [&] {
        // rest of body of function
    });
}

在澄清为什么需要它后进行编辑:

对于创建要放入 { } 和缩进的标记的特定用例,您可以创建一个专门命名的包装函数以明确发生了什么:

template <typename Functor>
void make_indented_block(Functor f) {
    auto indentToken = make_token();
    f();
}

【讨论】:

  • 谢谢;我知道我可以做到这一点,但这是我真正想避免的事情之一。
【解决方案4】:

我们这里有经典的XY problem

所以对于 C# 代码:

using (make_token())
{
  // my code here
}

创建一个类令牌:

class token {
public:
    token() { /* calling make_token(); */ }
    ~token() { /* destroying token */ }
};

然后使用它:

{
    token tok;
    // some stuff here
    {
        token tok;
        // some other stuff here
    }
}

所以

  1. 这种用法对于 C++ 开发人员来说很清楚,并且您的 C++ API 对他们来说很容易使用。
  2. 您关于创建唯一变量名的问题的论点是错误的,如图所示,您可以使用相同的名称。
  3. 您不必告诉任何人不要使用此变量,因为它只有构造函数和析构函数。

如有必要,您可以将其放入宏中,但我个人觉得它更难使用。

【讨论】:

  • 使用宏,您还可以通过合并__LINE__ 来生成(希望)唯一名称。
【解决方案5】:

好吧,您可能认为您可以接收该函数返回的值;

void use_token()
{
  auto nonsense = make_token();
  // extra code
}

即使这样,您是否知道(C++17 之前的版本)...当 RVO 不发生时,仍然有可能在那里调用两个析构函数?

将其作为The Quantum Physicist's answer 所说的const 引用是最好的出路。

【讨论】:

    【解决方案6】:

    我感觉到你的痛苦,auto = make_token() 会很有用。不过……

    您可能遇到了 XY 问题。你不应该这样做吗:

    with_token([&]{ ... });
    

    即,令牌生成器/构造器采用 lambda?如果您不想在 lambda 中编写 return 以从创建令牌的实际函数返回,这应该可以工作。

    另一种方法是,如果您只是不想让任何人“知道名字”,那就是臭名昭著的 for 模式:

    template<typename T>
    struct Keeper
    {
        const T t;
        char b;
        Keeper(const T& t_)
            : t(t_) {}
        char* begin() { return &b; }
        char* end()   { return begin() + 1; }
    };
    
    template<typename T>
    auto make_keeper(const T& t)
    {
        return Keeper<T>(t);
    }
    
    void f()
    {
        for (char c : make_keeper(make_token()))
        {
            // now try to name t or Keeper<T>(t) here
        }
    }
    

    如果您愿意,您可以添加移动示意图和完美的正向。

    【讨论】:

      猜你喜欢
      • 2020-03-05
      • 2011-05-20
      • 1970-01-01
      • 2020-08-10
      • 2016-10-29
      • 2017-11-07
      • 2021-06-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多