• C++: static member have to be defined seperately


    如果这样写c++的单例:

    class Singleton {
    private:
        static Singleton * singleton;
    private:
        Singleton() {
        }
    
    public:
        static Singleton *getinstance() {
            if(singleton == NULL) {
                Singleton::singleton = new Singleton();
            }
    
            return singleton;
        }
    };
    
    
    int main() {
        Singleton * p = Singleton::getinstance();
        return 0;
    }

    就会有linker error(unsolved external `singleton`)。

    顾名思义,unsolved external 就是说linker找不到这个symbol。

    注意,这里不是compiler的错误,而是linker的错误。

    假如改成这样:

    class Singleton {
    private:
        static Singleton * singleton;
    private:
        Singleton() {
        }
    
    public:
        static Singleton *getinstance() {
            if(singleton == NULL) {
                Singleton::singleton = new Singleton();
            }
    
            return singleton;
        }
    };
    
    Singleton * Singleton::singleton = nullptr;      //多加了这个定义
    
    int main() {
        Singleton * p = Singleton::getinstance();
        return 0;
    }

    那么就会没问题了。

    为什么? 为什么要加多这一个定义?在 Singleton 类中不是已经定义了吗? 就算要给那个类静态变量赋值, 为什么不直接加上 Singleton::singleton = nullptr; 就行? 毕竟在类中已经有声明了,再在这里声明一次不会造成重复?毕竟,一般情况下,你不能这样: int x; int x; 

    其实这并不是C++这门语言的问题,而是这C++、C、Pascal这一类static language的问题。Java这种依靠虚拟机的语言就没有这样的问题(后面再说为什么)。

    在众多编程语言之中,有一种语言被称为Native Language,比如C、C++、Pascal。之所以称为Native,并不是因为他们可以被编译(Java也可以被编译,Python也可以),而是因为他们编译出来的东西可以直接变成机器码,然后由底层的操作系统执行。Java、Python、Perl、C#这一类依靠虚拟机来运作的语言就不行。这也是Native的英文意思所在。

    这就意味着,他们编译出来的OBJ文件是通用的。只要你所用的C++ compiler,C compiler,Pascal compiler遵循同一个OBJ文件格式的规范(比如ELF, PE, COFF等),那么同一个linker就可以将这些OBJ文件链接成一个可执行文件。

    所以,换句话说,各个编译单元对其他编译单元(compilation unit)可以一无所知。(即使编译器想知道也不行,因为你根本就不知道和你一起被链接的OBJ文件是不是同一门语言产生的)

    你可以用C写一份代码,编译成一个OBJ文件;然后用C++写一个,编译成一个OBJ文件;然后用Pascal写一份,编译成一个OBJ。然后,用一个链接器将他们链接起来,变成一个可执行文件。

    实际上,C++语言并不知道linker的存在(只是一般诸如Visual Studio,GCC等编译套件会自动调用linker罢了),它只负责将多份代码编译成多份OBJ文件,然后由一个linker将他们链接起来。

    这种编译模型(compilation model)就是所谓的分离编译

    分离编译有它的好处,但是也会引来很多问题。其中一个就是多重定义的问题。

    多重定义的基本含义如下:

    假如你写了三份C++代码:

    //file1.cpp
    int c ;
    //file2.cpp
    int c;
    //file3.cpp
    int c;

    然后一个main入口:

    //main.cpp
    int main() {}

    当你单独编译任意一份的时候,都会成功(用 gcc -c file1.cpp... 命令编译, -c  代表compile,但是不链接),生成 file1.o .... 文件。

    但是,当你用链接器将他们链接在一起的时候,就会有链接错误,这是因为你这里定义了三次变量 c ,当连接器将这四个编译单元(compilation unit)链接在一起的时候,它就会发现, c 被定义了多次,于是报错。这就是多重定义的问题。

    可以看出,这是因为,各个编译单元对其他编译单元(compilation unit)可以一无所知,所以,即使你定义了多次 c ,编译器也不知道。等到linker再来处理。

    回到上面的Singleton上面来。

    在上面的代码上,之所以要这样子 Singleton * Singleton::singleton = nullptr; 对一个类里的static member “重新” 定义一翻,也是因为这个原因。

    要知道,定义一个变量就等于给它分配了一定的内存空间。static member也是。但是static member是所有的 instance(实例)共有的,所以它有且只有一块内存空间。

    但是,一个类的声明通常是放在一个头文件中的,而一个头文件可以被包含多次,分成不同的编译单元编译,各个编译单元对另外的编译单元又是一无所知的,所以编译器这时候就不知道怎么处理这个static member了。假如编译器在类声明的时候给这个static member分配了内存空间(承认了这个定义,并且采取了措施),那么,假如包含这个类的头文件又被其他编译单元包含,则,这个static member就会被再次分配一次内存。这样一来,static member 就不static了。

    所以,为了解决这个问题,C++语言规定,在编译这个类的时候,对类里面的static member不做处理(定义),程序员应该在另外一个地方,单独地定义这个static member。

    那么Java 为什么就可以这样呢?为什么Java就不用对static member单独定义呢?很简单,因为Java都是独立的,自己的虚拟机,只有Java语言编译出来的东西才能在上面跑。即使Java按照上面所说的编译流程编译,发现了重复的static member,它只需要把这些重复的对象整合成一个即可(依靠它的runtime mechanism)。所以,在语言(语法)层面上,Java没有这样的问题(不需要另外对static member进行单独定义)。C#也是。

    多说一句,C++中的singleton一般都不这样写,一般是这样:

    class Singleton {
    private:
        Singleton(){
        }
        Singleton(Singleton&);
        Singleton& operator=(Singleton&);
    public:
        static Singleton& getInstance(){
            static Singleton instance;
            return instance;    
        }
    };

    :)

  • 相关阅读:
    网页版微信无法登录的解决办法
    pycharm运行过程中pycharm控制台和python控制台之间的切换
    随机梯度下降
    K-means聚类
    ubuntu16.04下安装.deb安装包
    过拟合和欠拟合
    从K近邻算法、距离度量谈到KD树、SIFT+BBF算法
    CAJViewer 去除右上角闪动的图标
    C# 的时间戳转换
    网页底部广告 可关闭
  • 原文地址:https://www.cnblogs.com/walkerlala/p/5701616.html
Copyright © 2020-2023  润新知