慎用少用c++的static和extern变量-几个常见错误

11 篇文章 0 订阅

普通的变量是局部的,只存活于它所在的那个大括号(局部作用域)里,程序运行到超出这个区域后就会被销毁。static修饰的静态变量由于存储于进程的全局数据区,即使程序运行超出了它所在的大括号,该变量依然保持。这貌似给写程序带来了一些便利,但由于类中的static变量是由该类的所有实例化对象所共有,这往往不是我们想要的。举几个常见的例子:

1、类函数里的static变量

比如要写一个周期判断函数,需要记录上一时刻的时间:

/* a.hpp */
#include <ros/ros.h>

class ClassA
{
private:
    ros::Time last_time; //需要在函数外定义
public:
    void func()
    {
        if ((ros::Time::now() - last_time) > ros::Duration(0.1))
        {
            //do something;
            last_time = ros::Time::now();
        }
    }
}
/* a.cpp */
#include "a.hpp"
int main(int argc, char **argv)
{
    ros::init(argc, argv, "test_node");
    ClassA a;
    while(true)
    {
        a.func();
        ros::spinOnce();
        ros::Duration(0.001).sleep();
    }
}

 

这个func函数为了完成计时功能,需要让类ClassA定义一个last_time。如果用static变量就会让func所需的变量仅出现在它的函数范围里,而不需要类里定义了

/* a.hpp */
#include <ros/ros.h>

class ClassA
{
public:
    void func()
    {
        static ros::Time last_time = ros::Time(0); //不需要在函数外先定义了
        if ((ros::Time::now() - last_time) > ros::Duration(0.1))
        {
            //do something;
            last_time = ros::Time::now();
        }
    }
}

这里程序在遇到static变量时会初始化一次为ros::Time(0),func执行完后last_time的值仍然保留,下次执行func的时候会跳过初始化。

但是要注意static变量会被所有调用func的地方共享,尤其是当func是一个类里的函数时,这个类在实例化为多个对象后,这多个对象仍然共享同一个static变量的last time,这其实是我们不希望的。比如:

/* a.cpp */
#include "a.hpp"
int main(int argc, char **argv)
{
    ros::init(argc, argv, "test_node");
    ClassA a1;
    ClassA a2;
    while(true)
    {
        a1.func(); // a1和a2共用了last_time
        a2.func(); // a1和a2共用了last_time
        ros::spinOnce();
        ros::Duration(0.001).sleep();
    }
}

这里a1和a2共用了last_time,这可能使得程序的计时功能变得混乱。所以在类函数里使用static变量时,要清楚它到底是属于什么范围,如果应当是属于对象的局部元素,就应该老老实实在类里声明,不能使用static来偷懒。

但有时我们就是要利用static变量的全局属性,比如我们可以经常看见下面的把类变成全局的做法:

class ClassA
{
public:
    static ClassA& global()
    {
        static ClassA global;
        return global;
    }

    void func()
    {
    }
}

ClassA::global().func();

这里ClassA::global()自动生成返回一个ClassA的对象,由于其是static静态的,所以只会初始化一次,那么所有调用ClassA::global()得到的就是一个唯一的全局ClassA对象。这里静态变量其实有点全局变量的意思了,但又比全局变量安全,因为它仅作用于这个类里。

2、类的static成员变量

同样的,类的成员变量如果用static来修饰,仍然是属于这个类的所有对象。比如:

class Var
{
    public:
        static int param_a;
};
int Var::param_a = 999; //类静态成员变量必须在类外初始化,一般是用一个单独的cpp文件初始化

Var var1;
Var var2;

这里var1和var2其实共用了param_a,二者任一对param_a的修改都会影响另外一者的param_a的值。类的静态成员变量甚至不需要实例化就可以直接使用,因为这个变量其实属于这个类,而非实际的对象

std::cout << Var::param_a << std::endl

所以不到万不得已,尽量不要用static成员变量,除非这个变量是一个常值。

class Var
{
    public:
        static constexpr int param_a = 1.0; // static constexpr数据成员必须在类内声明和初始化
};

3、头文件中单独的static变量

对于头文件中的static变量,这个变量会具有头文件所在编译单元(cpp源文件)的作用域,即每个包含该头文件的cpp源文件都会有一个独立的 static 变量实例。

这种做法通常是错误的,因为多个源文件包含同一个头文件,会导致每个源文件都拥有自己的 static 变量,而这不是 static 变量的本意(全局)。比如:

a.hpp

static a = 1.0;

Class1.hpp

#include "a.hpp"

class Class1
{
    public:
        Class1(int input) {a=input;}
        int get_static_a() {return a;}
};

Class2.hpp

#include "a.hpp"

class Class2
{
    public:
        Class2(int input) {a=input;}
        int get_static_a() {return a;}
};

main.cpp

#include "Class1.hpp"
#include "Class2.hpp"

int main(int argc, char **argv)
{
    Class1 class1(10);
    std::cout << class1.get_static_a() << std::endl; // 输出10

    Class1 class1_2(12);
    std::cout << class1_2.get_static_a() << std::endl; // 输出12
    std::cout << class1.get_static_a() << std::endl; // 输出12

    Class2 class2(20);
    std::cout << class2.get_static_a() << std::endl; // 输出20

    std::cout << class1.get_static_a() << std::endl; // 输出12
    std::cout << class1_2.get_static_a() << std::endl; // 输出12
}

可见,在a.hpp中定义的static变量a,被所有Class1.hpp定义的类的对象所共有,但是Class1.hpp和Class2.hpp定义的类的对象拥有独立的a,这其实既不像全局变量,也不像局部变量,是不应该采用的。

要使用真的全局变量,请用extern来声明。

4、extern真全局变量

要让一个变量变成真全局(进程内全局),使用extern变量。注意extern只是声明全局性,定义和初始化还得在单独的cpp文件中进行:

a.hpp

extern int a; //声明全局

a.cpp

#include "a.hpp"
int a = 1; //定义和初始化

b1.cpp

#include "a.hpp"
std::cout << a << std::endl;

b2.cpp

#include "a.hpp"
a = 2;
std::cout << a << std::endl;

这里在a.hpp中声明a的全局性,在a.cpp中定义。所有用到a全局变量的cpp文件都要引用a.hpp。不要在a.hpp中定义初始化a,因为链接b1.cpp和b2.cpp时会报错重复定义。

一个好的程序不应该出现很多extern全局变量,因为这些全局变量的名称很可能和一些局部变量重名。

  • 27
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值