如何实现在范围退出时恢复值的Scope Guard?

时间:2014-03-26 14:59:28

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

以下是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,但它的用途似乎不同而且更复杂。

2 个答案:

答案 0 :(得分:3)

假设makeValueScopeGuard实现为:

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

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

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

第二个问题是,当您使用std::forward时,您使用了std::move


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

答案 1 :(得分:1)

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

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

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

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

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