返回本地静态的函数和返回静态成员的函数之间有什么区别(对象大小、性能等)?

What are the differences (object size, performance, etc.) between a function that returns a local static and a function that returns a static member?

本文关键字:返回 函数 对象 性能 什么 静态 静态成员 之间 区别      更新时间:2023-10-16

我的派生类需要提供一个函数,该函数向调用方返回 std::vector&。

我可以声明一个静态成员并在构造函数或 CPP 文件的"全局范围"中初始化它。 我还可以在派生函数中声明一个本地静态并返回它。

第一个选项在代码的三个独立位置具有声明、初始化和返回功能,而第二个选项将所有三个元素合并到同一位置。 这些方法之间的对象大小、性能等有何区别?

通过谢尔盖阅读评论后,编辑从这里开始。 我正在编辑 PeterT 的代码示例,添加一个示例以显示构造函数利用push_back。 所以我必须向 SergeyA 承认初始化确实发生在全局范围内,如下所述,变量s_val出现在四个单独的位置。

//ex.h
#include <vector>
using intVec = std::vector<int>;
struct ex
{
ex();
static intVec s_val;
intVec& getVal();
};
//ex.cpp    
intVec ex::s_val = intVec(5);
ex::ex()
{
if (s_val.size() == 0) {
s_val.reserve(5);
s_val.push_back(1);
s_val.push_back(4);
s_val.push_back(0);
s_val.push_back(2);
s_val.push_back(3);
}
assert(s_val.size() == 5);
}
intVec& ex::getVal()
{
return s_val;
}

我想现代化和简化代码以使用初始值设定项列表。 听起来返回全局静态允许我以干净有效的方式做到这一点。 这是对的吗?

//ex.h
#include <vector>
using intVec = std::vector<int>;
struct ex
{
intVec& getVal();
};
//ex.cpp    
static intVec s_val = { 1, 4, 0, 2, 3 };
intVec& ex::getVal()
{
assert(s_val.size() == 5);
return s_val;
}

本地静态将在每次调用函数时产生初始化保护(互斥锁)的成本。这是由于 C++11 保证了静态的线程安全初始化,对于本地静态意味着访问序列化。

全局静态(还包括静态类成员)不会产生该开销,因为全局静态是在运行main()之前初始化的。

您可以在生成的程序集中看到初始化保护:

https://godbolt.org/z/BzdzvN

当你这样做时

//ex.h
#include <vector>
using intVec = std::vector<int>;
struct ex
{
static intVec s_val;
intVec& getVal();
};
//ex.cpp
intVec ex::s_val = intVec(5);
intVec& ex::getVal()
{
return s_val;
}

然后,用于实例化向量的初始化代码发生在main()

但如果你这样做

//ex.h
#include <vector>
using intVec = std::vector<int>;
struct ex
{
intVec& getVal();
};
//ex.cpp
intVec& ex::getVal()
{
static intVec s_val = intVec(5);
return s_val;
}

然后,静态变量(被调用std::vector的构造函数)的初始化在第一次调用函数时发生。

这在多线程上下文中可能是一个问题,它可能导致竞争条件,但在这种情况下,您将返回对静态std::vector的可变引用,因此我假设线程对于这种特定情况无关紧要。(参见 Nikos 对这个问题的回答,了解为什么这不再是一个问题)

那里有一些误解。如果声明类的静态成员,则无法在构造函数中初始化它。您显然可以在构造函数中分配它,但是每当创建新对象(可能多次)时都会分配它,这对于大多数应用程序可能没有任何意义。

如果声明静态成员,则应将其设为 constexpr 并就地初始化,或者在类定义之外初始化。后者使您暴露在静态初始化顺序惨败的所有荣耀中,除非您可以保证成员的编译时初始化。

函数本地静态没有初始化顺序问题,并且具有定义的顺序。但是,使用函数本地静态,每次调用函数时,您都会支付一个分支(预测)的微不足道的代价。对于大多数应用程序,这甚至不值得谈论。