【问题标题】:How to deal with static storage duration warnings?如何处理静态存储持续时间警告?
【发布时间】:2018-08-02 22:24:27
【问题描述】:

我是一个试图从书中学习 C++ 的新手。下面的代码按预期工作并产生输出,但在定义enginerandomInt 的两行中有警告:“使用静态存储持续时间初始化'engine'可能会引发无法捕获的异常。”

如果我将第 7 行和第 8 行放在 main() 中,警告就会完全消失,但 getNumber 将无法使用 enginerandomInt

我不知道如何修复这些警告。另外,也许更重要的是,在main() 之外的各个地方使用randomInt 的正确方法是什么?在main() 中声明它然后根据需要将其传递给函数是否合适?不知何故,main() 感觉不适合声明这些类型的事情。

我之前问过一个与此类似的问题,但我仍然难以理解,并提供了一个希望有用的示例。

// Loosely based on Fig. 6.12: fig06_12.cpp, C++ How To Program, Ninth Edition

#include <iostream>
#include <iomanip>
#include <random>

std::default_random_engine engine( static_cast<unsigned int>( time(nullptr) ) );
std::uniform_int_distribution<unsigned int> randomInt( 1, 6 );

int getNumber();

int main() {
    for ( unsigned int counter = 1; counter <= 10; ++counter ) {
        std::cout << std::setw( 10 ) << randomInt( engine );
        if ( counter % 5 == 0 )
            std::cout << std::endl;
    }
    std::cout << getNumber() << std::endl;
    return 0;
}

int getNumber () {
    return randomInt( engine );
}

输出:

/CLionProjects/Warning/cmake-build-debug/Warning
         3         5         6         3         3
         1         4         2         4         5
2

Process finished with exit code 0

【问题讨论】:

  • “在 main() 中声明它然后根据需要将其传递给函数是否合适?” - 是的。
  • 我认为这是一个很好的问题!

标签: c++ c++11 global-variables


【解决方案1】:

延迟初始化全局变量(例如您正在使用的变量)的一种方法是将它们包装在get-functions 中。

std::default_random_engine& getEngine()
{
   // Initialized upon first call to the function.
   static std::default_random_engine engine(static_cast<unsigned int>(time(nullptr)));
   return engine;
}

std::uniform_int_distribution<unsigned int>& getRandomInt()
{ 
   // Initialized upon first call to the function.
   static std::uniform_int_distribution<unsigned int> randomInt(1, 6);
   return randomInt;
}

然后使用getEngine()getRandomInt()而不是直接使用变量。

【讨论】:

  • 这被认为是一种好的做法吗?至于我,那只会让代码更复杂
  • 这很好,因为错误发生在函数调用时,初始化中的异常将传播给调用者,而不是在模块加载时。只需将您的全局包装在一个具有静态成员的函数中......并返回它。
  • 这不会导致每次调用getRandomInt() 时都初始化std::default_random_engine 吗?对我来说,这感觉像是不必要的开销。你能解释一下吗?
  • @Patrick 不,std::default_random_engine 被声明为静态的,所以只会在第一次调用时被初始化。
【解决方案2】:

使用全局变量是有问题的,除非绝对必要,否则避免使用它们是常识。详情见:

Are global variables bad?

您的问题标题还涉及非全局范围的静态存储持续时间变量(例如函数的静态局部变量);这些问题较少,但也会让您有些头疼,尤其是在多线程工作中。

底线:最好让你的函数只依赖于它们的参数,并且尽可能少地产生副作用。让我们用你的getNumber() 函数来做这件事:

template <typename Distribution>
typename Distribution::result_type getNumber (
    std::default_random_engine&  random_engine,
    Distribution&                distribution) 
{
    return distribution( random_engine );
}

int main()
{
    std::default_random_engine engine( static_cast<unsigned int>( time(nullptr) ) );
    std::uniform_int_distribution<unsigned int> randomInt( 1, 6 );

    for ( unsigned int counter = 1; counter <= 10; ++counter ) {
        std::cout << std::setw( 10 ) << randomInt( engine );
        if ( counter % 5 == 0 )
            std::cout << std::endl;
    }
    std::cout << getNumber( engine, randomInt ) << std::endl;
    return 0;
}

【讨论】:

    【解决方案3】:

    我同意其他关于全局对象有问题的答案。

    但是,您的问题具体是关于全局变量的初始化可能引发异常,反过来,没有 C++ 范围可以捕获此异常(据我所知)。 (此警告是 cert-err58-cpp 中的 clang-tidy 静态分析器。)

    根据我的经验,另一个鲜为人知的解决方案是,如果您可以控制用作全局的类的构造函数,则使构造函数被调用 noexcept

    我认为它有一些优点:

    首先,这迫使您思考是否可以避免执行可能失败的操作(例如分配或打开文件)

    struct TypeUsedAsGlobal {
        explicit TypeUsedAsGlobal(int n) noexcept {
            ... // body of the constructor that cannot fail
        } catch(...) {}
    ...
    };
    

    其次,如果您无法避免它们,它将迫使您在构造函数本身中处理错误。

    struct TypeUsedAsGlobal {
        explicit TypeUsedAsGlobal(int n) noexcept try : m_{n} {
            ... // body that can fail
        } catch(...) {
            ... // handle the error
            ... // cleanup as much as you can
            ... // print a message, and most likely std::terminate
        }
    ...
    };
    

    (我认为try/catch 的范围可以更小)。

    考虑到所有因素,这还不错,特别是如果该类型仅用于全局对象(不在范围内),因为好吧,如果全局对象(希望)是整个程序的基础,你能做什么不能一开始就建吗? (例如,因为文件不存在,或者全局对象所代表的资源并不真正存在或不足以满足程序的逻辑。)

    相对于带有static 变量的函数,这种方法的好处是您的程序会立即失败,而不是在一小时后第一次使用全局对象后失败。 当然,目前还不清楚一般一种方法是否优于另一种方法,这取决于。


    最后一点:拥有一个全局随机数生成器是个坏主意。 生成器应在需要或传递时创建。 从不同线程调用随机数生成器/分布是不安全的,但更重要的(数学上)原因是您必须非常小心地控制生成器的生命周期和状态。

    除了你正在使用的这本书的作者之外,没有其他人可以责备他们,因为他们的不良做法而臭名昭著。

    在此处查看更好的替代方案The Definitive C++ Book Guide and List

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-24
      • 1970-01-01
      • 1970-01-01
      • 2014-05-10
      • 2015-07-23
      • 2012-11-17
      • 1970-01-01
      • 2016-07-05
      相关资源
      最近更新 更多