简体   繁体   English

有什么方法可以延长C ++中临时对象的寿命?

[英]Is there any way to extend the lifetime of a temporary object in C++?

I wrote a scope guard which resets a value when the scope exits: 我写了一个作用域保护,它在作用域退出时会重置一个值:

template <class T>
struct ResetGuard
{
    T old_value;
    T& obj_to_reset;
    ResetGuard(T& obj_to_reset, const T& new_value) :
        old_value(obj_to_reset),
        obj_to_reset(obj_to_reset)
    {
        obj_to_reset = new_value;
    }

    ~ResetGuard() { obj_to_reset = old_value; }
};

When this scope guard is returned from a function, is there any way to prevent the immediate destruction of the scope guard if it wasn't saved? 从函数返回此范围卫士时,是否有任何方法可以防止如果未保存范围卫士,则立即销毁该范围卫士?

For example: 例如:

int GLOBAL_VALUE = 0;
ResetGuard<int> temporarily_set_global_value(int new_val) {
    return { GLOBAL_VALUE, new_val }; //updates the global variable
}
void foo() {
    //Ideally, someone calling this function
    //Wouldn't have to save the returned value to a local variable
    temporarily_set_global_value(15);
    std::cout << "GLOBAL_VALUE is " << GLOBAL_VALUE << std::endl;
}

The way it's written now, anyone calling one of the functions would have to remember to always save the ResetGuard to a local variable, otherwise it would immediately reset the value. 按照现在的编写方式,调用其中一个函数的任何人都必须记住始终将ResetGuard保存到本地变量,否则它将立即重置该值。

Some context around what I'm trying to do 关于我正在尝试做的事情的一些背景

I'm writing a library to format and manipulate strings. 我正在编写一个用于格式化和操作字符串的库。 I have one global variable controlling how floating point numbers are formatted. 我有一个全局变量来控制浮点数的格式。 I know that global variables are typically a terrible idea, but please bear with me. 知道全局变量通常是一个糟糕的主意,但请忍受我。

I made the decision to use a global variable carefully. 我决定谨慎使用全局变量。 The alternative to using a global variable would be to pass around the object containing the formatting specification. 使用全局变量的替代方法是传递包含格式规范的对象。 This option ultimately proved infeasible: my library is designed to work with any objects that provide a implicit conversion to std::string . 该选项最终被证明是不可行的:我的库旨在与任何提供隐式转换为std::string There's no way to pass formatting options (or any parameters, really) to an implicit conversion function. 无法将格式设置选项(或任何参数)传递给隐式转换函数。 Hence, I had to resort to using a global variable. 因此,我不得不诉诸于使用全局变量。

Is there any way to extend the lifetime of a temporary object in C++? 有什么方法可以延长C ++中临时对象的寿命?

Only one way, assign it to a variable (possibly a reference). 仅一种方式,将其分配给变量(可能是引用)。 If you don't want to burden users of the library, you can hide the details behind a macro. 如果不想给库的用户增加负担,可以将详细信息隐藏在宏后面。 While it's true that the uses of macros become few and far between, this is something you can only do with a macro. 确实,对宏的使用变得越来越少,但这是您只能对宏执行的操作。 For instance, here's how you'd do it with a sprinkle of GCC extensions: 例如,这是您如何使用大量GCC扩展程序的方法:

#define CONCAT(a, b) a##b
#define SCOPED_GLOBAL_VALUE(x) \
  auto&& CONCAT(_unused, __COUNTER__) __attribute__((unused)) = temporarily_set_global_value(x)

So now when users write: 所以现在当用户写:

SCOPED_GLOBAL_VALUE(15);

They get the variable, free of charge, with the expressiveness you wanted. 他们免费获得具有所需表达能力的变量。

But of course, there's a caveat. 但是,当然有一个警告。 Since we generate the variable name using the pre-processor, we can't use this macro in an inline function. 由于我们使用预处理程序生成变量名,因此无法在嵌入式函数中使用此宏。 If we do, we'll violate the one definition rule. 如果这样做,将违反一个定义规则。 So that's a thing to consider. 所以这是要考虑的事情。

Personally, I wouldn't stress over this. 就个人而言,我不会对此感到压力。 It's a common idiom to require named RAII object (think lock_guard ), so just being presented a properly named function would be straight forward for any savvy C++ programmer. 要求使用命名的RAII对象(想想lock_guard )是一种常见的习惯用法,因此只要提供一个正确命名的函数,对于任何精明的C ++程序员来说都是很直接的。

Sorry about my previous answer folks, what was I thinking? 对不起我以前的答案乡亲,我想什么? I should have read the question properly. 我应该已经正确阅读了这个问题。

So, of course, foo() has to return your ResetGuard object in order to extend its lifetime, and this is a good thing , not a bad thing. 因此,当然, foo()必须返回ResetGuard对象以延长其寿命, 这是一件好事 ,而不是一件坏事。

Firstly, it's hardly a burden on the caller. 首先,这几乎不会给调用者带来负担。 After all, all he / she has to do is: 毕竟,他/她要做的就是:

auto rg = foo ();

As a potential caller of foo() I would have absolutely no problem with that, and @melpomene's excellent suggestion in the comments above ( [[nodiscard]] ) can be used to ensure that callers don't forget to do this. 作为foo()的潜在调用者,我对此绝对没有问题,并且@me​​lpomene在上述注释中的出色建议( [[nodiscard]] )可用于确保调用者不会忘记这样做。

And why is forcing the caller to do this a good thing (apart from the fact that you have no choice in the matter anyway)? 以及为什么强迫调用者执行此操作是一件好事(除了事实是您别无选择)? Well, it gives the caller the opportunity to manage the lifetime of the scopeguard and that might be useful (will provide live demo soon). 好吧,它使调用者有机会管理示波器的寿命,这可能会很有用(将很快提供实时演示)。

As for the other answers here, I would most definitely not hide all this in a macro because that hides an important piece of information from potential callers of foo() . 至于这里的其他答案,我绝对不会将所有这些都隐藏在宏中,因为这会隐藏潜在的foo()调用者的重要信息。 Instead, I would use [[nodiscard]] to remind them of their responsibilities and leave it at that. 相反,我会使用[[nodiscard]]来提醒他们自己的责任,而[[nodiscard]]

[Edit] [编辑]

I have now spent a little time over at Wandbox polishing up the code to add the full set of recommended constructors / assignment operators and to demonstrate the use of [[nodiscard]] , which for me is the find of the day. 现在,我在Wandbox上花了一些时间来完善代码,以添加建议的构造函数/赋值运算符的完整集合,并演示[[nodiscard]] ,对我来说,这是今天的发现。

First, the modified class, done in the way (I believe) those who know are recommending. 首先,修改后的类,以(我相信)那些知道的人推荐的方式完成。 I can particularly see the importance of defining a proper move constructor (just think of the subtle bugs you might run into if you don't). 我尤其可以看到定义适当的move构造函数的重要性(只要考虑一下,否则可能会遇到的细微错误)。 Pinched some stuff ( = delete ) from JVApen, looks wise to me, TU JV. 从JVApen捏了一些东西( = delete ),对我来说,TU JV很明智。

#include <iostream>
#include <assert.h>

#define INCLUDE_COPY_MOVE_SWAP_STUFF

template <class T> class [[nodiscard]] ResetGuard
{
public:
    ResetGuard (T& obj_to_reset, const T& new_value) : old_value (obj_to_reset), obj_to_reset (obj_to_reset)
    {
        obj_to_reset = new_value;
    }

#ifdef INCLUDE_COPY_MOVE_SWAP_STUFF
   ResetGuard (const ResetGuard& copy_from) = delete;
   ResetGuard &operator= (const ResetGuard& copy_assign_from) = delete;
   ResetGuard &operator= (ResetGuard&& move_assign_from) = delete;  

    ResetGuard (ResetGuard&& move_from) : old_value (move_from.old_value), obj_to_reset (move_from.obj_to_reset)
    {
        assert (!move_from.defunct);
        move_from.defunct = true;
    }
#endif

    ~ResetGuard()
    {
        if (!defunct)
            obj_to_reset = old_value;
    }

private:
    T old_value;
    T& obj_to_reset;
    bool defunct = false;
};

Comment out #define INCLUDE_COPY_MOVE_SWAP_STUFF to see the compiler warning you get if you don't do all the things you're supposed to. 注释掉#define INCLUDE_COPY_MOVE_SWAP_STUFF以查看编译器警告,如果您未按预期执行所有操作,则会收到警告。

Test program: 测试程序:

int GLOBAL_VALUE = 0;

ResetGuard<int> temporarily_set_global_value (int new_val)
{
    return { GLOBAL_VALUE, new_val }; // updates GLOBAL_VALUE
}

void bad_foo()
{
    temporarily_set_global_value (15);
    std::cout << "GLOBAL_VALUE in bad_foo () is " << GLOBAL_VALUE << std::endl;
}

void good_foo()
{
    auto rg = temporarily_set_global_value (15);
    std::cout << "GLOBAL_VALUE in good_foo () is " << GLOBAL_VALUE << std::endl;
}

auto better_foo()
{
    auto rg = temporarily_set_global_value (15);
    std::cout << "GLOBAL_VALUE in better_foo () is " << GLOBAL_VALUE << std::endl;
    return rg;
}

int main ()
{
    bad_foo ();
    good_foo ();
    std::cout << "GLOBAL_VALUE after good_foo () returns is " << GLOBAL_VALUE << std::endl;

    {
        auto rg = better_foo ();
        std::cout << "GLOBAL_VALUE after better_foo () returns is " << GLOBAL_VALUE << std::endl;

        {
            auto rg_moved = std::move (rg);
            std::cout << "GLOBAL_VALUE after ResetGuard moved is " << GLOBAL_VALUE << std::endl;
        }            

        std::cout << "GLOBAL_VALUE after ResetGuard moved to goes out of scope is " << GLOBAL_VALUE << std::endl;
        GLOBAL_VALUE = 42;
    }

    std::cout << "GLOBAL_VALUE after ResetGuard moved from goes out of scope is " << GLOBAL_VALUE << std::endl;
}

Compiler output: 编译器输出:

prog.cc: In function 'void bad_foo()':
prog.cc:47:38: warning: ignoring returned value of type 'ResetGuard<int>', declared with attribute nodiscard [-Wunused-result]
     temporarily_set_global_value (15);
                                      ^
prog.cc:40:17: note: in call to 'ResetGuard<int> temporarily_set_global_value(int)', declared here
 ResetGuard<int> temporarily_set_global_value (int new_val)
                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
prog.cc:6:40: note: 'ResetGuard<int>' declared here
 template <class T> class [[nodiscard]] ResetGuard
                                        ^~~~~~~~~~

Program output: 程序输出:

GLOBAL_VALUE in bad_foo () is 0
GLOBAL_VALUE in good_foo () is 15
GLOBAL_VALUE after good_foo () returns is 0
GLOBAL_VALUE in better_foo () is 15
GLOBAL_VALUE after better_foo () returns is 15
GLOBAL_VALUE after ResetGuard moved is 15
GLOBAL_VALUE after ResetGuard moved to goes out of scope is 0
GLOBAL_VALUE after ResetGuard moved from goes out of scope is 42 

So there you have it. 所以你有它。 If you do all the things you're supposed to do (and I hope I have!) then everything works just fine, and it's all nice and efficient thanks to RVO and guaranteed copy elision so there's no need to worry about that either. 如果您做了所有您应该做的事情(我希望我能做得到!),那么一切都会很好,而且由于RVO和有保证的复制省略,一切都很好而又高效,因此也不必担心。

Live demo . 现场演示

Before answering your question, I like to provide the correct way of solving this problem in C++. 在回答您的问题之前,我想提供使用C ++解决此问题的正确方法。

template <class T>
struct [[nodiscard]] ResetGuard
{
    T old_value;
    T& obj_to_reset;
    bool enabled{true};

    ResetGuard(T& obj_to_reset, const T& new_value) :
       old_value(obj_to_reset),
       obj_to_reset(obj_to_reset)
    {
       obj_to_reset = new_value;
    }

    ResetGuard(ResetGuard &&rhs)
       : old_value(rhs.old_value)
       , obj_to_reset(obj_to_reset)
    {
        rhs.enabled = false;
    }
    ~ResetGuard()
    {
        if (enabled)
            obj_to_reset = old_value;
    }
    ResetGuard(const ResetGuard &) = delete;
    ResetGuard &operator=(const ResetGuard &) = delete;
    ResetGuard &operator=(ResetGuard &&) = delete;  
};

void foo() {
    auto guard = temporarily_set_global_value(15);
    std::cout << "GLOBAL_VALUE is " << GLOBAL_VALUE << std::endl;
}

The above code contains several interesting elements: 上面的代码包含几个有趣的元素:

  • [[nodiscard]] prevent creating temporaries without creating a variable to ensure scope [[nodiscard]]防止创建临时变量而不创建变量以确保范围
  • The member enabled: Prevent the Dtor of a temporary to have a side effect 已启用成员:防止临时Dtor产生副作用
  • Move constructor: The move constructor allows moving the ResetGuard into a different scope with the right handling. Move构造函数:move构造函数允许通过正确的处理将ResetGuard移至其他范围。 In this case, disabling the old ResetGuard 在这种情况下,禁用旧的ResetGuard

As an extra note, I like to point attention to a C++17 extension (previously allowed optimization), which is called Guaranteed Copy/Move Elision . 需要特别注意的是,我想指出一个C ++ 17扩展(以前允许的优化),称为扩展复制/移动保证 This will ensure that in practice no extra temporary instances will exist. 这将确保在实践中不存在额外的临时实例。

Back to your question: Is there any way to extend the lifetime of a temporary object in C++? 回到您的问题: 在C ++中,有什么方法可以延长临时对象的寿命?

Yes, thanks to N0345 (Proposal from 1993). 是的,多亏了N0345 (1993年的提案)。 This proposal allows extension of a temporary by capturing it by const reference. 该提议允许通过const引用捕获临时扩展。

const auto &guard = temporarily_set_global_value(15);

However, it is unclear to me how many instances you will have in total. 但是,我不清楚您总共将有多少个实例。 However, if you use the solution with the move constructor, this is no longer an issue. 但是,如果将解决方案与move构造函数一起使用,则不再是问题。 Further more, when you use compiler optimizations, this function could be inlined when implemented in the header. 此外,当您使用编译器优化时,在标头中实现时可以内联此函数。 That could eliminate all copies. 这样可以消除所有副本。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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