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

时间:2018-07-01 06:29:21

标签: c++ c++11 scope temporary scopeguard

我写了一个作用域警戒,当范围退出时会重置一个值:

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

从函数返回此范围卫士时,有什么方法可以防止如果未保存范围卫士而立即销毁?

例如:

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

现在的编写方式是,调用其中一个函数的任何人都必须记住始终将ResetGuard保存到本地变量,否则它将立即重置该值。

关于我正在尝试做的事情的背景

我正在编写一个用于格式化和操作字符串的库。我有一个全局变量来控制浮点数的格式。我知道全局变量通常是一个糟糕的主意,但请忍受。

我决定谨慎使用全局变量。使用全局变量的替代方法是传递包含格式规范的对象。该选项最终被证明是不可行的:我的库旨在与提供隐式转换为std::string的任何对象一起使用。无法将格式设置选项(或任何参数)传递给隐式转换函数。因此,我不得不诉诸于使用全局变量。

3 个答案:

答案 0 :(得分:1)

  

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

只有一种方法,将其分配给变量(可能是引用)。如果不想给库的用户增加负担,可以将详细信息隐藏在宏后面。确实,对宏的使用变得越来越少了,但这是您只能使用进行的操作。举例来说,您可以使用一些GCC扩展程序来做到这一点:

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

现在,当用户写:

SCOPED_GLOBAL_VALUE(15);

他们免费获取具有所需表达能力的变量。

但是,当然有一个警告。由于我们使用预处理程序生成变量名,因此无法在嵌入式函数中使用此宏。如果这样做,将违反一个定义规则。所以这是要考虑的事情。

就我个人而言,我不会对此感到压力。要求使用命名的RAII对象(想想lock_guard)是一种常见的习惯用法,因此只要提供一个正确命名的函数,对于任何精明的C ++程序员来说都是直接的。

答案 1 :(得分:1)

对不起,我以前的回答,我在想什么?我应该已经正确阅读了问题。

因此,foo()当然必须返回您的ResetGuard对象以延长其寿命,这是一件好事,不是一件坏事。 / p>

首先,这几乎不会给调用者带来负担。毕竟,他/她要做的就是:

auto rg = foo ();

作为foo()的潜在调用者,我对此绝对没有问题,可以使用以上评论([[nodiscard]]中的@melpomene的 excellent 建议来确保呼叫者不会忘记这样做。

为什么强迫调用者执行此操作是一件好事(除了事实上您别无选择之外)?好吧,它为调用者提供了管理示波器的生命周期的机会,这可能会有用(很快将提供实时演示)。

对于这里的其他答案,我绝对不会将所有这些都隐藏在宏中,因为这会隐藏来自foo()潜在调用者的重要信息。相反,我会使用[[nodiscard]]来提醒他们自己的职责,而不再赘述。

[编辑]

我现在在Wandbox上花了一些时间来完善代码,以添加推荐的构造函数/赋值运算符的完整集合,并演示[[nodiscard]]的用法,对我而言,这是今天的发现。

首先,修改后的类,以(我相信)那些知道的人推荐的方式完成。我尤其可以看到定义适当的move构造函数的重要性(只要考虑一下,否则可能会遇到的细微错误)。捏了一些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;
};

注释#define INCLUDE_COPY_MOVE_SWAP_STUFF会看到编译器警告,如果您不执行应做的所有事情,则会得到警告。

测试程序:

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

编译器输出:

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
                                        ^~~~~~~~~~

程序输出:

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 

因此,您已经拥有了它。如果您做了所有应该做的事情(我希望我能做到!),那么一切都会很好,而且由于RVO和有保证的复制省略,一切都很好,而且效率很高,因此也不必担心。< / p>

Live demo

答案 2 :(得分:1)

在回答您的问题之前,我想提供使用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;
}

上面的代码包含几个有趣的元素:

  • [[nodiscard]]阻止创建临时变量而不创建变量以确保范围
  • 已启用成员:防止临时Dtor产生副作用
  • Move构造函数:move构造函数允许通过正确的处理将ResetGuard移到另一个作用域中。在这种情况下,请禁用旧的ResetGuard

作为一个额外的说明,我想指出一个C ++ 17扩展(以前允许的优化),它称为Guaranteed Copy/Move Elision。这将确保在实践中不存在额外的临时实例。

回到您的问题:有什么方法可以延长C ++中临时对象的寿命吗?

是的,感谢N0345(1993年的提案)。该建议允许通过使用const引用捕获临时扩展名。

const auto &guard = temporarily_set_global_value(15);

但是,我不清楚您总共将有多少个实例。但是,如果将解决方案与move构造函数一起使用,则不再是问题。此外,当您使用编译器优化时,在标头中实现时可以内联此函数。这样可以消除所有副本。