搜索避免在类模板中创建字段的技巧

Searching for a trick to avoid creating a field in a class template

本文关键字:字段 创建 搜索      更新时间:2023-10-16

我有一个类似类的作用域保护(这是简化的测试用例):

template<void(*close)()>
struct Guard1
{
  template<typename O>
  Guard1(O open) { open(); }
  ~Guard1() { close(); }
};
void close() { std::cout << "close g1n"; }
int main()
{
  Guard1<close> g1 = [](){ std::cout << "open g1n"; };
}

我修改了它,以便关闭表达式也可以作为 lambda 给出:

class Guard2
{
  std::function<void()> close;
public:
  template<typename O, typename C>
  Guard2(O open, C close) : close(close)
  {
    open();
  }
  ~Guard2() { close(); }
};

int main()
{
  Guard2 g2(
      [](){ std::cout << "open g2n"; },
      [](){ std::cout << "close g2n"; });
}

但是,我必须引入一个额外的字段const std::function<void()>& close;将lambda从构造函数传递到析构函数。

有没有办法避免这个额外的字段,同时仍然保留lambda(以及使用时也很好的语法)?

由于您只想将其用作 ScopeGuard - 因此您可以确定对close()的常量引用或右值引用是有效的。您需要一个成员或基类,就像其他答案一样 - 但这并没有太大的区别。但是你可以把它作为你的lambda的右值参考,而不是std::function这是相当大的性能成本:

template <class Close>
class ScopeGuard {
public:
    template <typename Open>
    ScopeGuard(Open&& open, Close&& close) 
         : close(std::forward<Close>(close))
    {
        open();
    }
    ScopeGuard(ScopeGuard&& other) : close(std::move(other.close))
    {}
    ~ScopeGuard()
    {
        close();
    }
private:
    Close&& close;
};

为了使其更易于使用 - 具有以下make功能:

template <class Open, class Close>
auto makeScopeGuard(Open&& open, Close&& close)
{
    return ScopeGuard<Close>(std::forward<Open>(open), 
                             std::forward<Close>(close));
}

和用法:

#include <iostream>
using namespace std;
int main() 
{
   int i = 0;
   auto scope = makeScopeGuard([&i]{cout << "Open " << i++ << "n";}, 
                               [&i]{cout << "Close " << i++ << "n";});
   cout << "Bodyn";
}

输出:

Open 0
Body
Close 1

我验证了它适用于 gcc 和 clang,C++14 没有错误/警告。

这通常是不可能的。与函数指针不同,lambda 可以捕获并因此包含运行时状态。诚然,您的 lambda 没有,因此可以转换为函数指针,但这并不能使其成为模板参数。

如果你能接受一点作弊:将lambda塞进基类而不是字段中:

#include <iostream>
template<typename Base>
struct Guard1 : public Base
{
  template<typename O>
  Guard1(O open, Base base ) : Base(base) { open(); }
  Guard1(Guard1 const& rhs) : Base(static_cast<Base const&>(rhs)) { }
  ~Guard1() { (*this)(); }
};
template<typename O, typename C>
Guard1<C> makeGuard(O o, C c) { return Guard1<C>(o,c); }
int main()
{
  auto g1 = makeGuard([](){ std::cout << "open g1n"; },
                      [](){ std::cout << "close g1n"; } );
}

有没有办法避免这个额外的字段,同时仍然保留lambda(以及使用时也很好的语法)?

是的:如果你观察到,将开放函数传递给你的范围守卫没有任何好处(因此单一责任原则规定你不应该在那里拥有它)。

还应将该函数作为运行时参数传递,而不是模板参数。这将允许在客户端代码中使用更自然的语法。

应使类型独立于模板类型。这也将使客户端代码中的语法更加自然。

应确保析构函数不会引发。

class Guard final
{
public:
    Guard1(std::function<void()> at_scope_exit)
    : f_(std::move(at_scope_exit))
    {
    }
    ~Guard1() noexcept { try{ f_(); } catch(...) {} }
private:
    std::function<void()> f_;
};

然后,客户端代码应如下所示:

int x()
{
    operation.begin_transaction();
    Guard commit{ [&operation](){ operation.commit_transaction(); } };
    // do things & stuff here
}

也许你可以使用这个问题的答案。希望我没有错,但如果可以使用构造函数的指针,您可以将其传递给type_traits(查看该问题中的第一个答案),并获得第二个参数,这将是关闭函数,然后您可以别名它。

由于无法获取构造函数的指针,也许您可以使用其他成员函数来初始化您的对象?