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;
    }
};

:)

时间: 2024-10-29 19:10:09

C++: static member have to be defined seperately的相关文章

转:C语言中的static变量和C++静态数据成员(static member)

转自:C语言中的static变量和C++静态数据成员(static member) C语言中static的变量:1).static局部变量        a.静态局部变量在函数内定义,生存期为整个程序运行期间,但作用域与自动变量相同,只能在定义该变量的函数内使用.退出该函数后, 尽管该变量还继续存在,但不能使用它.        b.对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值.而对自动变量不赋初值,则其值是不定的.2).static全局变量        全局变量本身就是静

static 和 no static Member function学习

以下是做实验的一段代码: #include <iostream> using namespace std; typedef void (*p)(); class Object { public: static void s_fun_1() { cout << "static function 1\n"; } void fun_1() {cout << "no static function 1\n";} }; typedef vo

[C++] static member variable and static const member variable

static member variable[可读可写] 可以通过指针间接修改变量的值 static const member variable[只读] 压根就不可以修改变量的值,会报错

C++ static member functions

今天复习老师昨天讲的static member functions.总觉得这玩意儿存在真是莫名其妙,度娘之,发现网上讲的都十分不清楚.还是用Google从米国网站找到了答案. class Something { private: static int s_nValue; }; int Something::s_nValue = 1; // initializer int main() { // how do we access Something::s_nValue? } In this cas

Virtual Member Functions &amp; Static Member Function

如果一个 normalize() 是一个 virtual member function, 那么以下的调用: ptr->normalize(); 将会被内部转化为: (*ptr->vptr[1])(ptr); 其中:vptr 表示由编译器生成的指针, 指向 virtual table, 它被安插在每一个声明有(或继承自) virtual functinos 的 class object 中. 事实上其名称也会被 mangled, 因为在一个复杂的 class 派生体系中, 可能存在多个 vpt

Reading Effictive java: static member class (SMC) and nonstatic member class(NSC)

Misunderstanding of static member class : For most programmers, comparing to SMC ,we may be more familiar with static field . so we may analogously guess how SMC works according to static field's feature. That leads to a misunderstanding.--SMC may on

C++:关于class member声明为static的情况

2014.5.27 reference: C++ primer 5th, $7.6:Static Class Members TOPIC 1:一个类中的member(data member和function member)可以声明为static,需要申明为static的情况有一下原因: 1:使用的客观需要:需要某个member是associated with the class,not with individual objects(如银行class中的prime interest rate这个

C++ 静态存储周期(static storage duration)

拥有静态存储周期(static storage duration)的对象将会被一直保存到程序结束. 声明 存储类型说明符(static)用于声明对象拥有静态存储期(static storage duration). 存储类型说明符(static)只可以用于对象,函数和匿名联合体. 块作用域中不能声明静态函数,也不能声明静态函数参数. void func(static int tag){ cout<<tag<<endl; { static void nonStaticFunc(int

Const member functions in C++

Recently, I've started to review and learn C++ techniques. During the learning process, I followed a learn-by-example methodology, which I consider to be very effective. From now on, I'll use this blog to explain key points and interesting topics in