防止早期对象破坏

Preventing early object destruction

在 C++ 中,如果我写

token make_token() { return token{}; }

然后如下使用

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

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

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

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

为了减少混淆,我的 token 不是 一个词汇标记。我用作品 token 代替作品 cookie。它意味着在构造函数中做一些事情,等到它的范围结束,然后在它的析构函数中做一些事情。而已。顺便说一下,如果我用 C# 写这个,我会写类似

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

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

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

操作方法如下:

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

但是你必须 return 严格按值才能工作(你在你提供的代码中这样做)。

不信的小伙伴请自己尝试一下再攻击post。

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

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

即便如此,您是否知道(C++17 之前的版本)...当 RVO 未发生时仍有可能调用两个析构函数?

按照所说的const参考是最好的出路。

像这样:

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

确保事后洗手 :)

如果您能够使用 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();
}

我感觉到你的痛苦,auto = make_token()会有用。然而...

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

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

即令牌 generator/constructor 采用 lambda?如果您不想在创建令牌的实际函数中将 lambda 中的 return 写入 return,这应该工作

另一种方法是,如果你只是想让别人'know the name',臭名昭著的 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
    }
}

如果您愿意,您可以添加移动原理图和完善前向。

我们这里有 classical XY problem:

所以对于 C# 代码:

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

创建一个 class 令牌:

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. 你不必告诉任何人不要使用这个变量,因为它只有构造函数和析构函数。

如果有必要你可以把它放到宏里,但我个人觉得它更难用。