首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >C++20 ScopeGuard

C++20 ScopeGuard
EN

Code Review用户
提问于 2020-07-21 23:51:57
回答 1查看 1.7K关注 0票数 10

一段时间以来,我一直想在C++中使用延迟语句或C++,但直到最近,我一直认为C++没有它。然而,那时我发现了std::experimental::scope_exit,但是它有一个问题,那就是它没有在我使用的任何编译器中实现。因此,我决定实施它。

scope.hh

代码语言:javascript
运行
复制
#ifndef SCOPE_SCOPE_HH
#define SCOPE_SCOPE_HH

#include <utility>
#include <new>
#include <concepts>

namespace turtle
{

    template<typename EF> requires std::invocable<EF> && requires(EF x) {{ x() } -> std::same_as<void>; }
    struct scope_exit
    {
        constexpr scope_exit operator=(const scope_exit &) = delete;

        constexpr scope_exit operator=(scope_exit &&) = delete;

        template<typename Fn, typename =
        std::enable_if_t<!std::is_same_v<std::remove_cvref_t<Fn>, scope_exit>, int>,
                typename = std::enable_if_t<std::is_constructible_v<EF, Fn>, int>>
        constexpr explicit scope_exit(Fn &&fn) noexcept(std::is_nothrow_constructible_v<EF, Fn> ||
                                                        std::is_nothrow_constructible_v<EF, Fn &>)
        {
            set_functor(std::forward<Fn>(fn));
        }

        template<typename = std::disjunction<std::enable_if_t<std::is_nothrow_move_constructible_v<EF>, int>,
                std::enable_if_t<std::is_nothrow_move_constructible_v<EF>, int>>>
        constexpr scope_exit(scope_exit &&other) noexcept(std::is_nothrow_move_constructible_v<EF> ||
                                                          std::is_nothrow_copy_constructible_v<EF>)
        {
            set_functor_move(std::move(other));
        }

        constexpr scope_exit(const scope_exit &) = delete;

        constexpr ~scope_exit() noexcept
        {
            /* if active, call functor and then destroy it */
            if (!m_released) {
                m_functor();
                m_released = true;
            }
            m_functor.~EF();
        }

        constexpr void release() noexcept
        {
            m_released = true;
        }

        constexpr const auto& exit_function() noexcept
        {
            return m_functor;
        }

    private:
        union
        {
            EF m_functor;
            char m_functor_bytes[sizeof(EF)] = {};
        };
        bool m_released{false};

        template<typename Fn>
        constexpr void set_functor(Fn &&fn) noexcept(std::is_nothrow_constructible_v<EF, Fn> ||
                                                     std::is_nothrow_constructible_v<EF, Fn &>)
        {
            if constexpr(!std::is_lvalue_reference_v<Fn> && std::is_nothrow_constructible_v<EF, Fn>) {
                ::new(&m_functor) EF(std::forward<Fn>(fn));
            } else {
                try {
                    ::new(&m_functor) EF(fn);
                } catch (...) {
                    m_released = true;
                    fn();
                    throw;
                }
            }
        }

        constexpr void set_functor_move(scope_exit &&other) noexcept(std::is_nothrow_move_constructible_v<EF> ||
                                                                     std::is_nothrow_copy_constructible_v<EF>)
        {
            /* only preform construction if other is active */
            if (!other.m_released) {
                if constexpr(std::is_nothrow_move_constructible_v<EF>) {
                    ::new(&m_functor) EF(std::forward<EF>(other.m_functor));
                } else {
                    try {
                        ::new(&m_functor) EF(other.m_functor);
                    } catch (...) {
                        m_released = true;
                        other.m_functor();
                        other.release();
                        throw;
                    }
                }
                other.release();
            }
        }
    };
    template<typename EF>
    scope_exit(EF) -> scope_exit<EF>;
}

namespace turtle
{
    template<typename EF> requires std::invocable<EF> && requires(EF x) {{ x() } -> std::same_as<void>; }
    struct scope_fail
    {
        constexpr scope_fail operator=(const scope_fail &) = delete;

        constexpr scope_fail operator=(scope_fail &&) = delete;

        template<typename Fn, typename =
        std::enable_if_t<!std::is_same_v<std::remove_cvref_t<Fn>, scope_fail>, int>,
                typename = std::enable_if_t<std::is_constructible_v<EF, Fn>, int>>
        constexpr explicit scope_fail(Fn &&fn) noexcept(std::is_nothrow_constructible_v<EF, Fn> ||
                                                        std::is_nothrow_constructible_v<EF, Fn &>)
                                                        : m_uncaught_exceptions(std::uncaught_exceptions())
        {
            set_functor(std::forward<Fn>(fn));
        }

        template<typename = std::disjunction<std::enable_if_t<std::is_nothrow_move_constructible_v<EF>, int>,
                std::enable_if_t<std::is_nothrow_move_constructible_v<EF>, int>>>
        constexpr scope_fail(scope_fail &&other) noexcept(std::is_nothrow_move_constructible_v<EF> ||
                                                          std::is_nothrow_copy_constructible_v<EF>)
                                                          : m_uncaught_exceptions(other.m_uncaught_exceptions)
        {
            set_functor_move(std::move(other));
        }

        constexpr scope_fail(const scope_fail &) = delete;

        constexpr ~scope_fail() noexcept
        {
            /* if active and an exception happened, call functor. */
            if (!m_released && std::uncaught_exceptions() > m_uncaught_exceptions) {
                m_functor();
            }
            /* destroy functor */
            m_functor.~EF();
        }

        constexpr void release() noexcept
        {
            m_released = true;
        }

        constexpr const auto& exit_function() noexcept
        {
            return m_functor;
        }

    private:
        union
        {
            EF m_functor;
            char m_functor_bytes[sizeof(EF)] = {};
        };
        bool m_released{false};
        int m_uncaught_exceptions{0};
        template<typename Fn>
        constexpr void set_functor(Fn &&fn) noexcept(std::is_nothrow_constructible_v<EF, Fn> ||
                                                     std::is_nothrow_constructible_v<EF, Fn &>)
        {
            if constexpr(!std::is_lvalue_reference_v<Fn> && std::is_nothrow_constructible_v<EF, Fn>) {
                ::new(&m_functor) EF(std::forward<Fn>(fn));
            } else {
                try {
                    ::new(&m_functor) EF(fn);
                } catch (...) {
                    m_released = true;
                    fn();
                    throw;
                }
            }
        }

        constexpr void set_functor_move(scope_fail &&other) noexcept(std::is_nothrow_move_constructible_v<EF> ||
                                                                     std::is_nothrow_copy_constructible_v<EF>)
        {
            /* only preform construction if other is active */
            if (!other.m_released) {
                if constexpr(std::is_nothrow_move_constructible_v<EF>) {
                    ::new(&m_functor) EF(std::forward<EF>(other.m_functor));
                } else {
                    try {
                        ::new(&m_functor) EF(other.m_functor);
                    } catch (...) {
                        m_released = true;
                        other.m_functor();
                        other.release();
                        throw;
                    }
                }
                other.release();
            }
        }
    };
    template<typename EF>
    scope_fail(EF) -> scope_fail<EF>;
}

namespace turtle
{
    template<typename EF> requires std::invocable<EF> && requires(EF x) {{ x() } -> std::same_as<void>; }
    struct scope_success
    {
        constexpr scope_success operator=(const scope_success &) = delete;

        constexpr scope_success operator=(scope_success &&) = delete;

        template<typename Fn, typename =
        std::enable_if_t<!std::is_same_v<std::remove_cvref_t<Fn>, scope_success>, int>,
                typename = std::enable_if_t<std::is_constructible_v<EF, Fn>, int>>
        constexpr explicit scope_success(Fn &&fn) noexcept(std::is_nothrow_constructible_v<EF, Fn> ||
                                                        std::is_nothrow_constructible_v<EF, Fn &>)
                : m_uncaught_exceptions(std::uncaught_exceptions())
        {
            set_functor(std::forward<Fn>(fn));
        }

        template<typename = std::disjunction<std::enable_if_t<std::is_nothrow_move_constructible_v<EF>, int>,
                std::enable_if_t<std::is_nothrow_move_constructible_v<EF>, int>>>
        constexpr scope_success(scope_success &&other) noexcept(std::is_nothrow_move_constructible_v<EF> ||
                                                          std::is_nothrow_copy_constructible_v<EF>)
                : m_uncaught_exceptions(other.m_uncaught_exceptions)
        {
            set_functor_move(std::move(other));
        }

        constexpr scope_success(const scope_success &) = delete;

        constexpr ~scope_success() noexcept(noexcept(std::declval<EF&>()()))
        {
            /* if active and an exception did not happen, call functor. */
            if (!m_released && std::uncaught_exceptions() <= m_uncaught_exceptions) {
                m_functor();
            }
            /* destroy functor */
            m_functor.~EF();
        }

        constexpr void release() noexcept
        {
            m_released = true;
        }

        constexpr const auto& exit_function() noexcept
        {
            return m_functor;
        }

    private:
        union
        {
            EF m_functor;
            char m_functor_bytes[sizeof(EF)] = {};
        };
        bool m_released{false};
        int m_uncaught_exceptions{0};
        template<typename Fn>
        constexpr void set_functor(Fn &&fn) noexcept(std::is_nothrow_constructible_v<EF, Fn> ||
                                                     std::is_nothrow_constructible_v<EF, Fn &>)
        {
            if constexpr(!std::is_lvalue_reference_v<Fn> && std::is_nothrow_constructible_v<EF, Fn>) {
                ::new(&m_functor) EF(std::forward<Fn>(fn));
            } else {
                try {
                    ::new(&m_functor) EF(fn);
                } catch (...) {
                    m_released = true;
                    fn();
                    throw;
                }
            }
        }

        constexpr void set_functor_move(scope_success &&other) noexcept(std::is_nothrow_move_constructible_v<EF> ||
                                                                     std::is_nothrow_copy_constructible_v<EF>)
        {
            /* only preform construction if other is active */
            if (!other.m_released) {
                if constexpr(std::is_nothrow_move_constructible_v<EF>) {
                    ::new(&m_functor) EF(std::forward<EF>(other.m_functor));
                } else {
                    try {
                        ::new(&m_functor) EF(other.m_functor);
                    } catch (...) {
                        m_released = true;
                        other.m_functor();
                        other.release();
                        throw;
                    }
                }
                other.release();
            }
        }
    };
    template<typename EF>
    scope_success(EF) -> scope_success<EF>;
}

#endif //SCOPE_SCOPE_HH

以下是其中的一个例子:

main.cc

代码语言:javascript
运行
复制
#include <iostream>
#include <ctime>
#include <SDL.h>
#include "scope.hh"


int main(int, char*[])
{
    // Reseed rand
    std::srand(std::time(0));

    SDL_Init(SDL_INIT_VIDEO);              // Initialize SDL2

    // Create an application window with the following settings:
    SDL_Window* window = SDL_CreateWindow(
            "An SDL2 window",                  // window title
            SDL_WINDOWPOS_UNDEFINED,           // initial x position
            SDL_WINDOWPOS_UNDEFINED,           // initial y position
            640,                               // width, in pixels
            480,                               // height, in pixels
            SDL_WINDOW_OPENGL
    );

    // Check that the window was successfully created
    if (window == nullptr)
    {
        // In the case that the window could not be made...
        std::cerr << "Could not create window: " << SDL_GetError() << "\n";
        return 1;
    }

    // Clean up when exiting the scope
    turtle::scope_exit cleanup{[&]{
        SDL_DestroyWindow(window);
        SDL_Quit();
    }};

    // An exception could be thrown
    if(std::rand() % 4 == 0)
    {
        throw;
    }

    // The window is open: could enter program loop here

    SDL_Delay(3000);  // Pause execution for 3000 milliseconds, for example

    return 0;
}
EN

回答 1

Code Review用户

回答已采纳

发布于 2020-07-24 07:04:19

Bugs

没有必要要求x()完全返回void -实际上,任何返回类型都可以,因为我们可以放弃结果。因此,对structs的要求应该简化为template <std::invocable EF>

此代码存在问题:

std::disjunction,int>,std::enable_if_t,int>>>

  • std::disjunction不像你想的那样工作;
  • moves应该是copy
  • 使用std::enable_if_t的SFINAE需要依赖于函数模板的模板参数。

所以应该是

代码语言:javascript
运行
复制
template <typename EF2 = EF, typename = std::enable_if_t<
       std::is_nothrow_move_constructible_v<EF2>
    || std::is_nothrow_copy_constructible_v<EF2>
>>

或者只使用requires

你需要特例推荐信。unions包含参考资料是格式不正确的IIRC。

不要将事情标记为constexpr,除非规范这么说,如果您的目标是一致性--添加constexpr的标准禁止实现。

无虫

如前所述,在许多情况下,您可以使用requires而不是enable_if

您不需要使用union-simulated std::optional来存储退出函数,因为所有的范围保护(包括非活动的)都会保持其退出函数的活力。(每个注释)对于移动构造函数,使用std::move_if_noexcept进行noexcept分派行为;例如:

代码语言:javascript
运行
复制
scope_exit(scope_exit&& other)
    noexcept(std::is_nothrow_move_constructible_v<EF> ||
             std::is_nothrow_copy_constructible_v<EF>)
    requires std::is_nothrow_move_constructible_v<EF> ||
             std::is_copy_constructible_v<EF>
    : m_functor(std::move_if_noexcept(other.m_functor))
{
    other.release();
}
票数 5
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/245846

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档