简体   繁体   中英

Preventing early object destruction

In C++, if I write

token make_token() { return token{}; }

and then use it as follows

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

without assigning a token to a variable, token 's destructor fires before extra code executes. How can I get the destructor to only fire at the end of the function without having to make a variable?

Note: I want to completely avoid making a variable. I know I can do auto& t = make_token() or similar, but I want to avoid precisely this by returning something (I don't know what) that doesn't have the destructor fired immediately.

Why I want this: basically, in my app (a compiler for a programming language) I have these things called tokens. A token's constructor can put a { and indent, and its destructor can then put } and un-indent. I thought it a good idea to set up functions which return these tokens by value, but I don't actually want to assign them to any value, since the tokens are useless and have no functions.

To alleviate confusion , my token is not a lexical token. I use the work token in lieu of the work cookie . It's meant to do something in the constructor, wait until the end of its scope, and then do something in its destructor. That's it. By the way, if I was writing this in C#, I would write something like

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

and it would work as intended. But it turns out that something so simple is difficult in C++.

Yes. You can use a constant reference. This is called most important const in C++, and it's a feature that's not widely known.

Here's how you do it:

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

But you have to return strictly by value for this to work (you do that in the code you provided).

People who don't believe this, please try it yourself before attacking the post.

Like this:

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

Make sure you wash your hands afterwards :)

If you're able to use C++11 or later, you could write a template function something like:

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

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

Edit after the clarification of why it's wanted:

For the specific use case of creating a token to put in { } and indent, you could create a wrapper function specifically named to make it clear what's happening:

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

We have classical XY problem here:

So for C# code:

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

create a class token:

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

then use it:

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

So

  1. This usage will be clear for C++ developers and your C++ API will be easy to use for them.
  2. Your argument about problem creating unique variable name is wrong, as shown you can use the same name.
  3. You do not have to tell anybody not to use this variable, as it only has constructor and destructor.

If necessary you can put that into macro, but personaly I would find it more difficult to use.

Well, you may think you can receive the value returned by that function;

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

Even with this, did you know that (Pre-C++17) ... There was still possibility for two destructor calls there when RVO doesn't take place?

Taking it by const reference as The Quantum Physicist's answer says is the best way out.

I feel your pain, auto = make_token() would be useful. However...

You might have the XY-problem. Shouldn't you do instead:

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

Ie, the token generator/constructor taking a lambda? This should work if you don't want to write return within the lambda to return from the actual function creating the token.

Another approach is, if you just want no-one to 'know the name', the notorious for-pattern:

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

You might add move schematics and perfect fwd if you wish.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM