【问题标题】:C++ Core Guidelines for static member variables静态成员变量的 C++ 核心指南
【发布时间】:2021-02-17 04:32:10
【问题描述】:

我的类中有一个私有静态向量,它保存指向从它创建的所有对象的指针。这是必要的,因为每个对象都需要访问所有其他对象的信息才能执行一些计算:

// Header file:
class Example {
public:
    Example();
private:
    static std::vector<const Example*> examples_;
};
// Cpp file:
std::vector<const Example *> Example::examples_ = {};
Example::Example() {
    // intialization
    examples_.emplace_back(this);
}
void Example::DoCalc() {
    for (auto example : examples_) {
        // do stuff
    }
}

clang-tidy 指出我违反了 C++ 核心准则,即:“变量 'examples_' 是非常量且全局可访问的,请考虑将其设为 const (cppcoreguidelines-avoid-non-const-global-variables)”。

就我个人而言,我没有看到我的代码与核心指南中的示例代码有相似之处,尤其是因为变量位于类内部并且是私有的。实现此功能的“正确”方式是什么?如果可以避免,我不想从 clang-tidy 禁用此检查。

【问题讨论】:

  • 可能和SOIF有关。如果你有 C++17,定义静态成员 inline,看看警告是否消失。
  • 我从事 C++ 工作已经很长时间了。我从来没有想过可变静态类成员存在根本性的问题。该文件提到的唯一潜在问题是静态初始化命令失败。这是事实,但不是否认所有静态类成员的理由。
  • 我想知道如果你在课堂上使用static inline std::vector&lt;const Example*&gt; examples_; 然后从cpp 文件中删除std::vector&lt;Example *&gt; Example::examples_ = {}; 会发生什么。您仍然收到警告吗?
  • @SamVarshavchik 和数据竞争,以及您隐藏依赖项的事实
  • 使用 gcc,这不会编译,因为您在这里省略了 conststd::vector&lt;const Example *&gt; Example::examples_ = {};。但我想这只是一个错字,而不是你真正的问题。

标签: c++ static-members clang-tidy cpp-core-guidelines


【解决方案1】:

就我个人而言,我认为我的代码与核心指南中的示例代码没有相似之处

您有一个变量可供每个线程访问,对Example 的用户隐藏。与普通全局变量的唯一区别是它是private,即您不能使用名称 Example::examples_Example 之外引用它。

注意

规则是“避免”,而不是“不要使用”。

实现此功能的“正确”方式可能是您拥有它的方式,但我强烈建议您重新设计“每个对象都需要访问所有其他对象的信息以执行一些计算”,以便您传递std::vector&lt;const Example*&gt;到需要它的地方,跟踪所有相关的(尤其是活跃的)Examples 使用它们的地方。

替代方案: [...] 另一种解决方案是将数据定义为某个对象的状态,将操作定义为成员函数。

警告:注意数据竞争:如果一个线程可以访问非本地数据(或通过引用传递的数据),而另一个线程执行被调用者,我们可能会发生数据竞争。每个指向可变数据的指针或引用都是潜在的数据竞争。

【讨论】:

  • “与普通全局变量的唯一区别是它是私有的。” 但这是一个根本的区别,直击核心引用的指南。
  • @AsteroidsWithWings 仍然存在数据竞争和远距离行动问题。它们已本地化为 Example,但仍然存在
  • 不管怎样,clang-tidy 是用一个不适用的规则来抱怨,这就是这个问题的意义所在。这实际上已经在 LLVM 问题跟踪器上提出过。
  • @AsteroidsWithWings 与范围不受约束的全局变量相同的竞争。比赛不会改变,只是有多少代码可能导致它。你当然是对的,clang-tidy 过度概括了 C++ 核心准则规则,但它仍然是一个有效的警告。
  • @Caleth 对,我们可以通过引入线程来选择其他方法来进行此类中断,因为它根本没有互斥。不过,这是一个巨大的滑坡论点,与这个问题没有任何关系! :)
【解决方案2】:

你做的很好。这实际上就是 class-static 的目的。有些人会出于不相关的原因推荐替代方案,这可能值得考虑……但不是因为 clang-tidy 在这里告诉你的任何事情。

您遇到了clang-tidy bug #48040。您可以看到这一点,因为它的消息传递是错误的:该向量 不是“全局可访问的”,至少在访问规则的意义上不是,因为它被标记为 private(尽管它在整个翻译过程中都是全局存在的单位,这很好)。

您的代码与引用的核心指南无关。

【讨论】:

  • 哦,错误报告很好发现 :)
  • 谢谢!我现在将在 .clang-tidy 文件中禁用它
  • 我将其称为核心指南中的错误。 static 数据成员与全局变量有一个非常相似的问题
  • @Caleth 不是错误 :)。它已经在待办事项部分,请参阅我更新的答案中的链接。
  • @WernerHenze 我想clang-tidy bug 将作为非bug/不会修复的错误被关闭,或者在该原型规则充实之前保持打开状态
【解决方案3】:

一个可能的解决方案是强制每个访问Example::examples_ 的客户端通过一个函数。然后将examples 作为静态变量放入该函数中。这样,对象将在第一次调用该函数时被创建 - 独立于任何全局对象构造顺序。

// Header file:
class Example {
public:
    Example();
private:
    std::vector<const Example*>& examples();
};
// Cpp file:
std::vector<Example *>& Example::examples()
{
    static std::vector<Example *> examples_;
    return examples_;
};
Example::Example() {
    // intialization
    examples().emplace_back(this);
}
void Example::DoCalc() {
    for (auto example : examples()) {
        // do stuff
    }
}

当然,如果您确定全局对象没有问题,并且确定在构造过程中没有其他全局对象正在访问Examples::examples_,则可以忽略该警告。这只是一个指导方针,您不需要严格遵守。

正如 Asteroids With Wings 指出的那样,准则 I.2 不适用于您的代码。但请注意,CoreGuidelines 也打算禁止静态成员,请参阅To-do: Unclassified proto-rules

避免使用静态类成员变量(竞争条件、几乎全局变量)

【讨论】:

  • 指南实际上并没有说明这个案例。
  • (我同意您提出的替代方案是更安全的代码。)
  • @AsteroidsWithWings 好点,这些词没有提到静态类成员,但是这些都遭受与全局变量相同的初始化顺序问题。我将在 CppCoreGuideline repo 上打开一个问题。
  • 谢谢。学习正确的做事方法总是好的。我意识到代码不是线程安全的,但它很可能永远不会有多个线程。 + 每个对象中的计算通过外部机制同步。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-08
  • 2016-05-20
  • 1970-01-01
相关资源
最近更新 更多