【问题标题】:Preventing users from creating unnamed instances of a class [duplicate]防止用户创建类的未命名实例[重复]
【发布时间】:2017-04-14 12:55:16
【问题描述】:

对于许多 RAII "guard" 类,将其实例化为匿名变量根本没有意义:

{
    std::lock_guard<std::mutex>{some_mutex};
    // Does not protect the scope!
    // The unnamed instance is immediately destroyed.
}

{
    scope_guard{[]{ cleanup(); }};
    // `cleanup()` is executed immediately!
    // The unnamed instance is immediately destroyed.
}

来自this article

C++ 中的匿名变量具有“表达式范围”,这意味着它们在创建它们的表达式结束时被销毁。


有什么方法可以阻止用户在没有名字的情况下实例化它们? (“阻止”可能太强了——“让它变得非常困难”也是可以接受的)。 em>

我可以想到两种可能的解决方法,但它们在使用类时会引入语法开销:

  1. detail 命名空间中隐藏类并提供一个宏。

    namespace detail
    {
        class my_guard { /* ... */ };
    };
    
    #define SOME_LIB_MY_GUARD(...) \
        detail::my_guard MY_GUARD_UNIQUE_NAME(__LINE__) {__VA_ARGS__}
    

    这可行,但 hackish

  2. 只允许用户通过高阶函数使用守卫。

    template <typename TArgTuple, typename TF>
    decltype(auto) with_guard(TArgTuple&& guardCtorArgs, TF&& f)
    {
        make_from_tuple<detail::my_guard>(std::forward<TArgTuple>(guardCtorArgs));
        f();
    }
    

    用法:

    with_guard(std::forward_as_tuple(some_mutex), [&]
    {
        // ...
    });
    

    当保护类的初始化具有“流利”语法时,此解决方法不起作用:

    {
        auto _ = guard_creator()
                     .some_setting(1)
                     .some_setting(2)
                     .create();
    }
    

还有更好的选择吗?我可以使用 C++17 功能。

【问题讨论】:

  • 我认为你问错了方向。 C++ 开发人员应该理解为什么lock_guard 存在,以及为什么只要它保护的范围存在,它就应该保持存在。试图通过 API 或内部实现来强制执行这一点是多余的..
  • @Danh:另一个问题的答案是我的“宏观”解决方法。如果我更明显地表明我正在寻找基于非宏观的解决方案,您会考虑重新提出这个问题吗? @David:对于lock_guard 的例子,我同意。不过,我正在使用“流利的”语法进行一些非分配异步链生成,在这种情况下,拥有匿名链是很自然的。但是你仍然需要一个名字,否则链的存储很快就会死掉。它不像lock_guard那么明显,我想防止这个错误。
  • @VittorioRomeo 这个问题还有其他答案。而且,恕我直言,如果我们可以将所有关于此的讨论集中在一个地方,那就更好了。不管怎样,this answer 看起来很有趣
  • @VittorioRomeo 所以基本上,您使用的是堆栈分配版本的std::async + future::then
  • “被实例化为匿名变量根本没有意义”,除非结果绑定到引用并延长生命周期。这是在 C++17 之前编写 make_lock_guard 的唯一方法。一种可能性是强制使用make_ 函数,然后将其标记为[[nodiscard]]

标签: c++ c++17 raii


【解决方案1】:

如果能够充分利用 C++17 的潜力,您可以将使用静态工厂函数的想法扩展为有用的东西:保证复制省略使静态工厂函数即使对于不可移动的类也成为可能,并且 [ [nodiscard]] 属性提示编译器在返回值被忽略时发出警告。

class [[nodiscard]] Guard {
  public:
    Guard(Guard& other) = delete;
    ~Guard() { /* do sth. with _ptr */ }
    static Guard create(void* ptr) { return Guard(ptr); }
  private:
    Guard(void* ptr) : _ptr(ptr) {}
    void* _ptr;
};

int main(int, char**) {
  Guard::create(nullptr);
  //auto g = Guard::create(nullptr);
}

Compile in Compiler Explorer

【讨论】:

    【解决方案2】:

    防止类被实例化的规范方法是将其构造函数设为private。要真正获得所需的实例之一,请调用 static 方法,该方法返回对构造对象的引用。

    class Me {
    public:
        static Me &MakeMe() { return Me(); }
    private:
        Me();
    }; // Me
    

    这当然没有帮助 - 但它可能会让程序员暂停!

    int main() {
        Me(); // Invalid
        Me m; // Invalid
        Me::MakeMe(); // Valid - but who'd write that?
        Me m = Me::MakeMe();
    } // main()
    

    我知道这不是您描述的Guard 实例的直接模拟 - 但也许您可以调整这个概念?

    【讨论】:

    • 我认为这甚至不应该编译;您将右值绑定到左值引用。
    【解决方案3】:

    我想到的唯一明智的方法是让用户将guard_creator::create 的结果传递给一些guard_activator,它以左值引用作为参数。

    这样,类的用户没有办法,只能使用名称创建对象(大多数开发人员都会这样做的理智选项),或者new 然后取消引用(疯狂的选项)

    例如,您在 cmets 中说您在非分配异步链创建器上工作。我可以想到一个看起来像这样的 API:

    auto token = monad_creator().then([]{...}).then([]{...}).then([]{...}).create();
    launch_async_monad(token); //gets token as Token&, the user has no way BUT create this object with a name 
    

    【讨论】:

    • MSVC++ 向她致以问候
    • @Danh 他们很久以前就修复了这个故障
    • rextester.com/DALS87730 在有和没有/Za 的情况下都编译成功。 1900表示这是VS2015
    • @Danh 您可以使用 /we4239 编译以将该扩展名视为错误
    • 对于我的特殊情况,这看起来很有希望。我会尽快尝试 - 谢谢
    【解决方案4】:

    您可以使用可扩展的 lint 工具,例如 Vera++ https://bitbucket.org/verateam/vera/wiki/Home 它可以让您对代码进行 lint,您可以使用 Python 或 tcl(我更喜欢 Python)创建新规则

    一个可能的流程是 - 在每次提交之后,您的 CI 系统(例如 Jenkins)将运行一个作业来执行 Vera++ 并验证此类疏忽,一旦失败,就会向提交者发出一封邮件。

    【讨论】:

      猜你喜欢
      • 2017-01-07
      • 2015-04-02
      • 1970-01-01
      • 1970-01-01
      • 2010-10-30
      • 1970-01-01
      • 2017-02-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多