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

问题描述

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;

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

posted @ 2024-04-12 15:41  HL棣  阅读(9)  评论(0编辑  收藏  举报