一次性写“ if”的最优雅方式

nad*_*ada 134 c++ if-statement c++17

由于C ++ 17可以编写一个if块,因此将完全执行一次,如下所示:

#include <iostream>
int main() {
    for (unsigned i = 0; i < 10; ++i) {

        if (static bool do_once = true; do_once) { // Enter only once
            std::cout << "hello one-shot" << std::endl;
            // Possibly much more code
            do_once = false;
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

我知道我可能对此有过多的思考,还有其他方法可以解决此问题,但是仍然-是否可以以这种方式编写此文本,因此do_once = false最后不需要?

if (DO_ONCE) {
    // Do stuff
}
Run Code Online (Sandbox Code Playgroud)

我在考虑一个辅助函数,do_once()其中包含static bool do_once,但是如果我想在不同的地方使用相同的函数呢?可能这是时间和地点#define吗?我希望不是。

Aco*_*orn 142

用途std::exchange

if (static bool do_once = true; std::exchange(do_once, false))
Run Code Online (Sandbox Code Playgroud)

您可以更短一些来反转真实值:

if (static bool do_once; !std::exchange(do_once, true))
Run Code Online (Sandbox Code Playgroud)

但是,如果您经常使用这种方法,请不要幻想并创建一个包装器:

struct Once {
    bool b = true;
    explicit operator bool() { return std::exchange(b, false); }
};
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

if (static Once once; once)
Run Code Online (Sandbox Code Playgroud)

不应在条件之外引用该变量,因此名称对我们没有多大帮助。从其他语言(例如Python)中获得启发,这些语言赋予_标识符特殊的含义,我们可以这样写:

if (static Once _; _)
Run Code Online (Sandbox Code Playgroud)

进一步的改进:利用BSS部分(@Deduplicator),避免在我们已经运行时写入内存(@ShadowRanger),并在要进行多次测试时给出分支预测提示(例如,如问题所示):

// GCC, Clang, icc only; use [[likely]] in C++20 instead
#define likely(x) __builtin_expect(!!(x), 1)

struct Once {
    bool b = false;
    explicit operator bool()
    {
        if (likely(b))
            return false;

        b = true;
        return true;
    }
};
Run Code Online (Sandbox Code Playgroud)

  • 我知道宏在C ++中非常令人讨厌,但这看起来真是太该死了:`#define ONLY_ONCE if(static bool DO_ONCE_ = true; std :: exchange(DO_ONCE_,false))`用作:`ONLY_ONCE {foo (); }` (32认同)
  • 许多软件使用名称_来标记可翻译的字符串。期待有趣的事情发生。 (13认同)
  • 对变量使用`_`将会是非Pythonic的。您不会在稍后要引用的变量中使用`_`,而只是将值存储在您必须提供变量的位置,但是您不需要该值。当您只需要一些值时,通常用于解包。(还有其他用例,但它们与一次性价值用例完全不同。) (7认同)
  • 我的意思是,如果您编写“一次”三次,那么在if语句中使用它超过三遍是值得的。 (5认同)

lub*_*bgr 91

也许不是最优雅的解决方案,并且您看不到任何实际的解决方案if,但是标准库实际上涵盖了这种情况:请参阅std::call_once

#include <mutex>

std::once_flag flag;

for (int i = 0; i < 10; ++i)
    std::call_once(flag, [](){ std::puts("once\n"); });
Run Code Online (Sandbox Code Playgroud)

这样做的好处是这是线程安全的。

  • 嗯,看到“ call_once”对我来说意味着您想打电话一次。疯狂,我知道。 (17认同)
  • 这是可行的,但并不是简单的解决方案,而是针对多线程应用程序。对于某些简单的事情来说,这是太过分了,因为它使用了内部同步-所有这些事情都将由简单的if解决。他不是在寻求线程安全的解决方案。 (11认同)
  • @MichaelChourdakis我同意你的观点,这太过分了。但是,这是值得了解的,尤其是知道有可能表达您正在做的事情(“一次执行此代码”),而不是将内容隐藏在可读性较差的if-trikery后面。 (9认同)
  • 在这种情况下,我并不了解std :: call_once。但是,使用这种解决方案,您需要为使用该std :: call_once的每个地方声明一个std :: once_flag,不是吗? (3认同)
  • @SergeyA,因为它使用内部同步-所有这些都可以通过简单的if解决。这是一种比要求的事情更惯用的方式。 (3认同)
  • @LightnessRacesinOrbit我查看了gcc中的call_once实现,只要您支持TLS,就不会出现显式同步。我无法快速找到LLVM的实现。 (2认同)
  • @SergeyA不能说它是如何实现的,但是并发是必需的。在[此处](https://en.cppreference.com/w/cpp/thread/call_once)上了解更多有关它的信息。请注意,尽管static var示例的init(但不是check或set)也是线程安全的。(实际上,链接文章的字面意思是_“即使从多个线程调用时,函数本地静态函数的初始化也仅保证发生一次,并且可能比使用std :: call_once的等效代码更有效。” _) (2认同)
  • 我知道由于同步会产生一些开销,但这似乎是迄今为止最优雅的方法。我看不出在`if`语句之前必须声明一个变量的问题。它比`if(static bool do_once = true; std :: exchange(do_once,false))`和类似的解决方案更加清晰易读,并且其意图比创建包装器结构更清晰。我对C ++不太了解,但是所有其他涉及静态变量的解决方案似乎都非常棘手。 (2认同)

Seb*_*ach 51

C ++确实具有一个内置的控制流原语,该原语已经由“((before-block; condition; after-block)”)组成:

for (static bool b = true; b; b = false)
Run Code Online (Sandbox Code Playgroud)

或更骇客,但更短:

for (static bool b; !b; b = !b)
Run Code Online (Sandbox Code Playgroud)

但是,我认为此处介绍的任何技术都应谨慎使用,因为它们(不是吗)非常普遍。

  • 具有讽刺意味的是,对于某个问题,最优雅的解决方案之一是“循环”,在这种情况下,某些代码只应该执行一次。+1 (3认同)
  • 我会避免使用“ b =!b”,这看起来不错,但是您实际上希望该值为false,因此应首选“ b = false”。 (2认同)
  • 请注意,如果受保护的块非本地退出,它将再次运行。这甚至可能是合乎需要的,但与所有其他方法不同。 (2认同)

Bat*_*eba 29

在C ++ 17中,您可以编写

if (static int i; i == 0 && (i = 1)){
Run Code Online (Sandbox Code Playgroud)

为了避免i在循环体内玩弄。i从0开始(由标准保证的),并且后表达;i1第一次它被评估。

请注意,在C ++ 11中,您可以使用lambda函数实现相同的功能

if ([]{static int i; return i == 0 && (i = 1);}()){
Run Code Online (Sandbox Code Playgroud)

这也具有一点优势,即i不会泄漏到回路主体中。

  • 虽然“静态int i;”可能(我确实不确定)是保证将“ i”初始化为“ 0”的情况之一,但在这里使用“ static int i = 0;”要清楚得多。 (9认同)
  • 无论如何,我都同意初始化器是理解的好主意 (7认同)
  • 我很伤心地说 - 如果想在一个叫的#define来call_once的放在左右,它会更可读 (4认同)
  • @Bathsheba Kyle没有,所以您的主张已被证明是错误的。为了使代码清晰,添加两个字符需要多少钱?来吧,您是“首席软件架构师”;你应该知道这一点:) (4认同)
  • 如果您认为拼出变量的初始值与clear相反,或者表明“正在发生一些奇怪的事情”,那么我认为您无济于事;) (4认同)
  • 我更喜欢用`bool`:`if(static bool hasRun = false;!hasRun &amp;&amp;(hasRun = true))`。而且,是的,我也认为应该有一个#define:#define RUN_ONCE if(static bool hasRun = false;!hasRun &amp;&amp;(hasRun = true))`,允许我们说出RUN_ONCE {/ *东东* /}` (3认同)
  • @KyleWillmon:不,这是标准所保证的。 (2认同)
  • @LightnessRacesinOrbit:但是我看不出来。事实恰恰相反。代码中的多余位表示正在发生一些时髦事件。与`if(n == true)`相距不远,但是我们早就确定我是不正常的。 (2认同)

Wax*_*rat 14

static bool once = [] {
  std::cout << "Hello one-shot\n";
  return false;
}();
Run Code Online (Sandbox Code Playgroud)

此解决方案是线程安全的(与许多其他建议不同)。

  • 您是否知道lambda声明中的()是可选的(如果为空)? (3认同)

moo*_*eep 9

您可以将一次性操作包装在实例化的静态对象的构造函数中,以代替条件对象。

例:

#include <iostream>
#include <functional>

struct do_once {
    do_once(std::function<void(void)> fun) {
        fun();
    }
};

int main()
{
    for (int i = 0; i < 3; ++i) {
        static do_once action([](){ std::cout << "once\n"; });
        std::cout << "Hello World\n";
    }
}
Run Code Online (Sandbox Code Playgroud)

或者您可能确实坚持使用宏,看起来可能像这样:

#include <iostream>

#define DO_ONCE(exp) \
do { \
  static bool used_before = false; \
  if (used_before) break; \
  used_before = true; \
  { exp; } \
} while(0)  

int main()
{
    for (int i = 0; i < 3; ++i) {
        DO_ONCE(std::cout << "once\n");
        std::cout << "Hello World\n";
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 8

Like @damon said, you can avoid using std::exchange by using a decrementing integer, but you have to remember that negative values resolve to true. The way to use this would be:

if (static int n_times = 3; n_times && n_times--)
{
    std::cout << "Hello world x3" << std::endl;
} 
Run Code Online (Sandbox Code Playgroud)

Translating this to @Acorn's fancy wrapper would look like this:

struct n_times {
    int n;
    n_times(int number) {
        n = number;
    };
    explicit operator bool() {
        return n && n--;
    };
};

...

if(static n_times _(2); _)
{
    std::cout << "Hello world twice" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)


Dam*_*mon 7

虽然std::exchange按@Acorn的建议使用是最惯用的方式,但交换操作不一定便宜。尽管当然可以保证静态初始化是线程安全的(除非您告诉编译器不要这样做),所以在存在static关键字的情况下,任何有关性能的考虑都是徒劳的。

如果你关注微优化(如使用C ++往往是人),你可能还有划伤bool和使用int代替,这将允许您使用后递减(或更确切地说,增量,因为不像bool递减的int饱和为零...):

if(static int do_once = 0; !do_once++)
Run Code Online (Sandbox Code Playgroud)

过去bool曾经有增减运算符,但是很早以前就已弃用它们(C ++ 11?不确定吗?),并且在C ++ 17中将其完全删除。不过,您可以递减int就可以了,它当然可以作为布尔条件工作。

奖励:您可以实施do_twicedo_thrice类似地...