【问题标题】:Is there a way to raise a compile time error when calling a given function several times in C++?在 C++ 中多次调用给定函数时,有没有办法引发编译时错误?
【发布时间】:2020-12-21 21:46:25
【问题描述】:

在 C++ 中有没有办法设计一个函数/向它添加一些“属性”,这样在代码中多次调用它会引发编译时错误?

提供一点背景/动机:我在 Mbed-OS 上编程,我犯了几个看起来像这样的错误:

rtos::Thread thread;

[lots of code]

thread.start(persistent_function_1);

[lots of code in a setup function]

thread.start(persistent_function_2);

这具有(逻辑)结果,应该允许在程序的生命周期内执行的persistent_function_1 只能在线程被重新用于运行persistent_function_2 之前执行。我花了很长时间才找到这个错误,我想知道是否可以对我的thread.start 函数做一些事情,以确保如果我再次犯这种错误,我会得到一个编译器错误。

【问题讨论】:

  • 必须有一种方法可以将thread.start 包装在一个模板中,该模板设置一个布尔值或计数器并在静态断言中测试该布尔值。当然,有些雅虎会在没有模板的情况下调用thread.start,你也好不到哪里去。
  • 是的,这已经是一个不错的改进了。我同意这仍然可能失败,但代码库中可能有“不应使用裸线程声明”的政策。

标签: c++ compile-time error-checking


【解决方案1】:

我认为没有办法直接强制 C++ 语言在编译时检测 start() 的双重调用(换句话说,我认为 @user4581301 的建议行不通):静态断言属性你需要以某种方式改变实体。我确定您可以使用clang 编写自定义检查器,但我想这不是您想要的。显然,有可能有一个运行时断言,它报告一个已经 start()ed 的线程再次启动。同样,这似乎不是您所追求的。

“显而易见”的解决方案是不要在函数中使用“[lots of code]”。事实上,std::thread 通过强制在对象声明和它的开始之间没有 代码完全回避了这个问题:std::thread 在构造时开始。在对象声明和开始之间带有“[lots of code]”的设置类似于

my::thread thread([&]{
        [lots of code]
        return persistent_function_1;
    }());

需要注意的是,您需要设置各种无序的变量。也就是说,首选方法是在它实际启动的位置声明thread 对象:

[lots of code]
my::thread thread(persistent_function_1);

在这两种情况下,my::thread 都是 rtos::thread 的简单包装器,它不会公开单独的 start() 方法。由于我不知道为什么rtos::thread 将构造和start() 分开,并且一个合理的原因可能是能够设置各种线程参数,因此实际上对my::thread 的构造函数使用两个单独的参数可能是合理的:

  1. my::thread::properties 实体作为参数的函数,允许对thread 对象进行必要的操作。
  2. 要启动的函数。

也就是说,类似

my::thread thread([](my::thread::properties& properties) {
    [lots of code manipulating the properties]
    },
    persistent_function_1);

这样,仍然可以操作thread,但不能两次start() 一个线程。

【讨论】:

  • 老实说,我并不是 100% 确定它可以完成,但每次我认为某事无法完成时,似乎某个聪明人会放弃一个模板元程序来完成它。通常模板是如此疯狂,它比它试图治愈的更糟糕,但我试图说明的真正意义是,无论你做什么来防止重新调用start,有人会通过直接调用start 来绕过。唯一明智的做法是在代码审查期间使预防变得不必要或使错误显而易见。
  • @user4581301:我同意经常有人出现并找到一种方法来做一些以前认为不可能的事情(很久以前当我发现一个函数模板推断出一个内置数组,即现在内置数组的std::end() 是什么)。但是,我认为没有办法更改名称的静态属性,即使涉及宏,我也看不到如何使用不同的名称。因此,我解决该问题的建议是使用不同的界面。不过,要正确地做到这一点,我需要更多地了解rtos::thread
【解决方案2】:

一种选择是将线程包装在一个新的管理器对象中,大致形状为

class thread_manager {
  rtos::Thread thread;
  const std::function<...> execution_function;
  /* .
     .
     . */

public:
  thread_manager(rtos::Thread _thread, std::function<...> function, ...)
    : thread { _thread }
    , execution_function { function }
    , ...

  void start();
}

并且不允许任何其他线程使用(这可以基于封装来证明,尽管正如 cmets 中所指出的,雅虎总是有风险的)。

【讨论】:

  • C++(像大多数编程语言一样)旨在帮助程序员避免意外错误。无法阻止“雅虎”(也就是那些故意绕开某人费力执行的约束的人)把事情搞砸。如果可以编写代码来阻止某些事情,那么其他人可以重写该代码或找到一些邪恶的解决方法。这就是编译器无法强制执行的非技术强制(文档、政策、解雇故意违反规则的人)的原因。
【解决方案3】:

目前没有检测出现两次的表达式的机制。但是你可以折磨编译器来接近目标

namespace
{
    template<int>
    struct once
    {
        once() {}
        friend void redefine() {}
    };
}

#define ONCE(expr) (once<__COUNTER__>{}, (expr))

如果 ONCE 在同一个 TU 中出现两次,编译器将抱怨重新定义 redefine

ONCE(thread.start(persistent_function_1));  // ok
ONCE(thread.start(persistent_function_2));  // error

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-04
    • 1970-01-01
    • 2012-02-17
    • 1970-01-01
    • 2012-01-08
    • 1970-01-01
    • 2021-03-04
    • 2017-08-21
    相关资源
    最近更新 更多