首页 > 编程语言 >C++陷阱 — C++ auto变量类型推导

C++陷阱 — C++ auto变量类型推导

时间:2024-04-12 15:44:06浏览次数:8  
标签:推导 Instance auto single1 C++ instance 单例 SingleClass

问题描述

C++ 使用auto类型声明一个单例对象的引用时,通过该auto变量来访问单例,是否等同于使用单例类::Instance()来访问单例呢?

试看如下的例子:

#include <stdint.h>
#include <iostream>
#include <string>
#include <map>

using namespace std;

class SingleClass
{
public:
    ~SingleClass(){}

    static SingleClass & Instance(){ 
        static SingleClass s_instance; 
        return s_instance;
    }

    void insert(pair<string, int> && p){
        m_map.insert(p);
    }
    void printMap(){
        for (auto& n : m_map)
            cout << n.first << ":" << n.second <<endl;
    }
private:
    SingleClass(){};
    map<string, int> m_map;
};

int main()
{
    auto single1 = SingleClass::Instance();
    SingleClass & single2 = SingleClass::Instance();

    single1.insert({"A", 1});
    single1.insert({"B", 2});
    single1.insert({"C", 3});
    single1.insert({"D", 4});
    SingleClass::Instance().insert({"E", 5});
    single2.insert({"F", 6});
    
    cout << "========auto single1======="<<endl;
    single1.printMap();

    cout << "========SingleClass::Instance()======="<<endl;
    SingleClass::Instance().printMap();

    cout << "========single2======="<<endl;
    single2.printMap();
    
    return 0;
}
========auto single1=======
A:1
B:2
C:3
D:4
========SingleClass::Instance()=======
E:5
F:6
========single2=======
E:5
F:6

预想中single1single2SingleClass::Instance()一样都是指向同一个单例对象,实际测试结果却并不是这样,single2SingleClass::Instance()指向的是同一个单例对象,single1指向的却是另一个单例!

为什么会这样,是获取单例函数的问题吗?没有正确的构造单例?打印这些单例引用对象的地址看一下

class SingleClass
{
    static SingleClass & Instance(){ 
        static SingleClass s_instance; 
        cout << "get instance:" << &s_instance <<endl;
        return s_instance;
    }
    ...
}

int main()
{
    auto single1 = SingleClass::Instance();
    SingleClass & single2 = SingleClass::Instance();

    single1.insert({"A", 1});
    single1.insert({"B", 2});
    single1.insert({"C", 3});
    single1.insert({"D", 4});
    SingleClass::Instance().insert({"E", 5});
    single2.insert({"F", 6});
    
    cout << "========auto single1=======:"<< &single1 <<endl;
    single1.printMap();

    cout << "==SingleClass::Instance()==:" << &SingleClass::Instance()<<endl;
    SingleClass::Instance().printMap();

    cout << "==========single2=========:"<< &single2 <<endl;
    single2.printMap();
    return 0;
}

get instance:0x6052c0
get instance:0x6052c0
get instance:0x6052c0
========auto single1=======:0x7fff0934a430
A:1
B:2
C:3
D:4
get instance:0x6052c0
==SingleClass::Instance()==:0x6052c0
get instance:0x6052c0
E:5
F:6
==========single2=========:0x6052c0
E:5
F:6

很明显单例引用对象single1single2指向的内存地址不一致,也就是他们引用的不是同一个对象!

原因分析

局部静态变量的生命周期是整个程序周期,本代码是在一个线程中顺序执行,在一些弱顺序内存模型的cpu中,cpu(或者编译器)优化后的指令重排也仅影响多线程下的程序,所以本测试代码的单例构造函数不存在线程安全问题。

最有可能的问题是这行的代码,这里把一个引用重新赋值给一个auto变量时,猜测这里可能执行了拷贝构造。是否如此呢,我们测试一下。

auto single1 = SingleClass::Instance();

实现一下拷贝构造,看看打印输出:

SingleClass(SingleClass & r){
    cout << "copy construct:" << &r << endl;
};

输出

get instance:0x6042c0
copy construct:0x6042c0
get instance:0x6042c0
get instance:0x6042c0
========auto single1=======:0x7ffff734a8d0
A:1
B:2
C:3
D:4
========SingleClass::Instance()=======:0x6042c0
get instance:0x6042c0
E:5
F:6
========single2=======:0x6042c0
E:5
F:6

在第一个get instance的后面,紧接着打印了 copy construct
所以明显的, 单例构造后,在auto变量single1赋值时发生了拷贝构造,导致内存中存在两份“单例”。

总结

当单例构造返回的是引用类型时,且使用auto类型推导时,要特别注意禁用拷贝构造函数和赋值构造函数。

禁用方式有两种,一是使用delete关键字,二是可以声明为private

//单例类。需要禁用拷贝构造,否则在使用auto声明单例的引用时将会产生问题
SingleClass(SingleClass & r) = delete;

//一般单例也需要禁用赋值构造函数
SingleClass & operator = (const SingleClass &) = delete;

当单例构造返回的是指针时,只要保证线程安全,不禁用拷贝构造也不会存在上述的问题。

标签:推导,Instance,auto,single1,C++,instance,单例,SingleClass
From: https://www.cnblogs.com/HuangLiDi/p/18131454

相关文章

  • 深入解析decltype和decltype(auto)
    decltype关键字是C++11新标准引入的关键字,它和关键字auto的功能类似,也可以自动推导出给定表达式的类型,但它和auto的语法有些不同,auto推导的表达式放在“=”的右边,并作为auto所定义的变量的初始值,而decltype是和表达式结合在一起,语法如下:decltype(expr)var;它的语法像是函数调......
  • String类型转LPCTSTR -----理解C++中的字符串类型转换
    在看代码时,发现有时候会把string类型转换为LPCTSTR,刚开始不理解为什么要做这个转换,所以做了一些调查,现在记录如下是这样的,STRING是代表C++中的字符串string,而LPCTSTR代表的是Windows系统中的字符串类型。也就是说,这样转换的目的是为了把C++中的字符串string转换为Windows系......
  • C#开发AutoCAD插件多线程问题2种解决方法
    后台线程不允许操作界面,解决方案委托主线程来操作,在winform中用控件的Invoke方法。CAD插件里,可以用下面两种方法来实现: 方法一(推荐)://主线程:System.Threading.SynchronizationContextctx=null;ctx=Autodesk.AutoCAD.Runtime.SynchronizationContext.Current;if(ctx==......
  • @Autowired不显示黄线
    由于Spring更推荐使用构造器注入或者Setter注入,使用@Autowired进行字段注入会产生无法注入的黄线警告:不建议为了消除警告换为@Resource注解或者使用构造器注入或者Setter注入,这两种方式比较繁琐,不如字段注入简洁易用(Spring官方文档都在用),如果强迫症不想看到这个警告,可以......
  • 2022年蓝桥杯C++B组国赛-试题D-最大数字
    0.题目问题描述给定一个正整数N。你可以对N的任意一位数字执行任意次以下2种操作:将该位数字加1。如果该位数字已经是9,加1之后变成0。将该位数字减1。如果该位数字已经是0,减1之后变成9。你现在总共可以执行1号操作不超过A次,2号操作不超过......
  • 【论文随笔】深度推荐系统的自动化_一项调查(Automl for deep recommender systems_ A
    前言今天读的论文为一篇于2021年1月发表在ACMTransactionsonInformationSystems的论文,本文是一篇关于深度推荐系统自动化机器学习(AutoML)的综述,由RuiqiZheng、LiangQu、BinCui、YuhuiShi和HongzhiYin共同撰写。文章首先提出了一个抽象概念——AutoMLforDeepRecommende......
  • C++——线性动态规划
    线性动态规划引入:1.爬楼梯爬楼梯类型的问题可谓是线性DP的入门题目以及经典中的经典。我们先来看一下题目。爬楼梯题目描述有一天,三萩实在太无聊了,竟然无聊到去数台阶了。有一个楼梯一共有m级,刚开始三萩在第一级,他就想,若每次只能跨上一级或者二级,要走上m级,共有多少种走法?......
  • C++ 引用和指针:内存地址、创建方法及应用解析
    C++引用和指针创建引用引用变量是对现有变量的“别名”,它是使用&运算符创建的:stringfood="Pizza";//食物变量string&meal=food;//对food的引用现在,我们可以使用变量名food或引用名meal来引用食物变量:cout<<food<<"\n";//输出Pizzacout<<mea......
  • 推导式(OPPO23届秋招-后端真题)
    题面核心思想建立一个有向图从c作为起点dfs同时做访问标记时间复杂度o(n)然后所有访问过的都是能推导的时间复杂度o(n)最终复杂度o(n)代码importjava.util.*;publicclassMain{staticfinalintMAXN=(int)(1e4+10);staticList<Integer>[]next;......
  • 深入解析C++的auto自动类型推导
    关键字auto在C++98中的语义是定义一个自动生命周期的变量,但因为定义的变量默认就是自动变量,因此这个关键字几乎没有人使用。于是C++标准委员会在C++11标准中改变了auto关键字的语义,使它变成一个类型占位符,允许在定义变量时不必明确写出确切的类型,让编译器在编译期间根据初始值自动......