【问题标题】:How to implement Scope Guard that restores value upon scope exit?如何实现在范围退出时恢复价值的范围保护?
【发布时间】:2014-03-26 14:59:28
【问题描述】:

以下是否是 Scope Guard 的惯用 C++11 实现,它在范围退出时恢复值?

template<typename T>
class ValueScopeGuard
{
public:
    template<typename U>
    ValueScopeGuard(T& value, U&& newValue):
        _valuePtr(&value),
        _oldValue(std::forward<U>(newValue))
    {
        using std::swap;
        swap(*_valuePtr, _oldValue);
    }
    ~ValueScopeGuard()
    {
        if(_valuePtr)
        {
            using std::swap;
            swap(*_valuePtr, _oldValue);
        }
    }

    // Copy
    ValueScopeGuard(ValueScopeGuard const& other) = delete;
    ValueScopeGuard& operator=(ValueScopeGuard const& other) = delete;

    // Move
    ValueScopeGuard(ValueScopeGuard&& other):
        _valuePtr(nullptr)
    {
        swap(*this, other);
    }
    ValueScopeGuard& operator=(ValueScopeGuard&& other)
    {
        ValueScopeGuard(std::move(other)).swap(*this);
        return *this;
    }

private:
    T* _valuePtr;
    T _oldValue;

    friend void swap(ValueScopeGuard& lhs, ValueScopeGuard& rhs)
    {
        using std::swap;
        swap(lhs._valuePtr, rhs._valuePtr);
        swap(lhs._oldValue, rhs._oldValue);
    }
};

template<typename T, typename U>
ValueScopeGuard<T> makeValueScopeGuard(T& value, U&& newValue)
{
    return {value, std::forward<U>(newValue)};
}

它可以用来临时改变一个值,如下所示:

int main(int argc, char* argv[])
{
    // Value Type
    int i = 0;
    {
        auto guard = makeValueScopeGuard(i, 1);
        std::cout << i << std::endl; // 1
    }
    std::cout << i << std::endl; // 0

    // Movable Type
    std::unique_ptr<int> a{new int(0)};
    {
        auto guard = makeValueScopeGuard(a, std::unique_ptr<int>{new int(1)});
        std::cout << *a << std::endl; // 1
    }
    std::cout << *a << std::endl; // 0

    return 0;
}

这样的简单实用程序是否已经在某个库中实现了?我查看了 Boost.ScopeExit,但它的预期用途似乎不同且更复杂。

【问题讨论】:

  • 对构造函数使用'template ValueScopeGuard(T& value, U&& newValue)'。同样,为“makeValueScopeGuard”使用单独的模板类型。这将允许完美转发工作并允许左值和右值。
  • @John5342:啊,我知道现在发生了什么! T&amp;&amp; 不是“通用”引用,因为 T 是从第一个参数推导出来的。已纳入建议。

标签: c++11 move-semantics rvalue-reference


【解决方案1】:

假设makeValueScopeGuard 实现为:

template< typename T >
ValueScopeGuard<T> makeValueScopeGuard( T& t, T&& v )
{
    return ValueScopeGuard<T>(t,std::move(v));
}

不,范围保护的实现不是很好,因为当您将左值作为第二个参数传递时它会失败:

int kk=1;
auto guard = makeValueScopeGuard(i, kk);

第二个问题是你使用了std::forward,而你应该使用std::move


正如this question and answers 所示,人们通常使用 lambdas 来实现范围保护。

【讨论】:

  • 关于 l 值的好点,这绝对是一个问题。但是,我不太明白为什么在这种情况下std::move 更可取?我的意图是将参数转发给构造函数...
  • 关于您的编辑:我知道 lambda 解决方案。但是,对于我的实际用例,它似乎过于复杂,因为我只想设置值并在范围退出时恢复它。这就是我尝试实施的动机......
  • @kloffy 你需要std::move,而不是std::forward,因为那是一个右值——不是通用参考。
  • @kloffy 也许最好的办法是将构造函数更改为:ValueScopeGuard(T&amp; value, U newValue): 并使用 move。
【解决方案2】:

您的移动构造函数使指针成员未初始化,因此右值对象最终持有一个垃圾指针,它在其析构函数中取消引用。那是一个错误。您应该将其初始化为 nullptr 并在析构函数中检查 nullptr

对于这样的类型,我不希望移动分配是一个简单的交换,我希望右值最终不会拥有任何东西。所以我会像这样实现这个动作,所以右值最终是空的:

ValueScopeGuard& operator=(ValueScopeGuard&& other)
{
    ValueScopeGuard(std::move(other)).swap(*this);
    return *this;
}

makeValueScopeGuard 这个名字我不清楚它会改变值本身,我希望它只是复制当前值并在析构函数中恢复它。

就现有类型而言,我能想到的最接近的是Boost I/O state savers,它不会改变当前状态,它们只是复制并恢复它。

【讨论】:

  • 好收获!我已经修复了(回顾性地)明显的错误。感谢您的建议。我绝对可以在移动语义上使用更多练习。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-13
  • 1970-01-01
  • 2020-02-19
  • 2011-01-06
  • 2017-01-26
相关资源
最近更新 更多