我写了一个作用域警戒,当范围退出时会重置一个值:
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
的任何对象一起使用。无法将格式设置选项(或任何参数)传递给隐式转换函数。因此,我不得不诉诸于使用全局变量。
答案 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>
答案 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;
}
上面的代码包含几个有趣的元素:
作为一个额外的说明,我想指出一个C ++ 17扩展(以前允许的优化),它称为Guaranteed Copy/Move Elision。这将确保在实践中不存在额外的临时实例。
回到您的问题:有什么方法可以延长C ++中临时对象的寿命吗?
是的,感谢N0345(1993年的提案)。该建议允许通过使用const引用捕获临时扩展名。
const auto &guard = temporarily_set_global_value(15);
但是,我不清楚您总共将有多少个实例。但是,如果将解决方案与move构造函数一起使用,则不再是问题。此外,当您使用编译器优化时,在标头中实现时可以内联此函数。这样可以消除所有副本。