码迷,mamicode.com
首页 > 编程语言 > 详细

C++: static member have to be defined seperately

时间:2016-07-24 22:28:32      阅读:193      评论:0      收藏:0      [点我收藏+]

标签:

如果这样写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;    
    }
};

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

:)

 

C++: static member have to be defined seperately

标签:

原文地址:http://www.cnblogs.com/walkerlala/p/5701616.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!