使用单件构造器(SingletonConstructor)顺序初始化单件

使用单件构造器(SingletonConstructor)保证单件顺序初始化

对于单件模式时,目前主要有两种方法,即静态初始化以及动态初始化
对于动态初始化,由于锁(Double Check Lock)的加入,必然无法应用于高性能要求的场合。
对于静态初始化,单件的初始化顺序是不能保证的,如果单件直接存在依赖关系,这将导致初始化失败。
为了解决这个问题,引入一个单件构造器,由单件构造器来负责对各个单件进行显式的初始化

单件构造器(SingletonConstructor)定义如下:
1.单件构造器是所有其他单件的友元。
2.单件构造器是所影响域存在的唯一的静态初始化单件。

//Head file
class SingletonConstructor{
private:
    SingletonConstructor();
    ~SingletonConstructor();
    SingletonConstructor(SingletonConstructor const&);
    SingletonConstructor& operator=(SingletonConstructor const&);

    static SingletonConstructor s_SingletonConstructor_;
};

calss SingletonA{
public:
    friend class SingletonConstructor;
    SingletonA& Instance()
    {
        return *s_SingletonA_;
    }
private:
    SingletonA()
    {
        if (!s_SingletonA_)
            s_SingletonA_ = this;
    }

    ~SingletonA();
    SingletonA(SingletonA const&);
    SingletonA& operator=(SingletonA const&);

    static SingletonA *s_SingletonA_;
}

calss SingletonB{
public:
    friend class SingletonConstructor;
    SingletonB& Instance()
    {
        return *s_SingletonC_;
    }
private:
    SingletonB()
    {
        if (!s_SingletonB_)
        s_SingletonB_ = this;
    }
    ~SingletonB();
    SingletonB(SingletonB const&);
    SingletonB& operator=(SingletonB const&);
    
    static SingletonB *s_SingletonB_;
}

calss SingletonC{
public:
    friend class SingletonConstructor;
    SingletonC& Instance()
    {
    return *s_SingletonC_;
    }
private:
    SingletonC()
    {
        if (!s_SingletonB_)
        s_SingletonB_ = this;
    }
    ~SingletonC();
    SingletonC(SingletonC const&);
    SingletonC& operator=(SingletonC const&);
    static SingletonC *s_SingletonC_;
}

//Cpp file
SingletonConstructor SingletonConstructor::s_SingletonConstructor_;

SingletonConstructor::SingletonConstructor()
{
    m_SingleA = new SingletonA();
    m_SingleB = new SingletonB();
    m_SingleC = new SingletonC();
    //Or you can use std::vector<SingletonBase *> instead of m_Single*
}

SingletonConstructor::~SingletonConstructor()
{
    delete m_SingleC;
    delete m_SingleB;
    delete m_SingleA;
    //Or you can use std::vector<SingletonBase *> instead of m_Single*
}

单件构造器的思想在于显式初始化各个单件。

继续阅读 使用单件构造器(SingletonConstructor)顺序初始化单件

freeglut 简明安装指南 for windows

glut(GL Unity Tookit)是OpenGL的一套辅助开发函数库,但是由于缺乏更新以及非开源等原因,目前已经有12年没有维护,所以强烈建议更换到freeglut这个完全兼容的开源代替版本。

一、下载freeglut

freeglut的最新版本是2.8.1(Released: 5 April 2013)
可以从http://freeglut.sourceforge.net/获得其最新版本。

二、编译freeglut

freeglut提供了对windows平台良好的编译支持,在freeglut-2.8.1\VisualStudio目录下可以找到2008~2012版本的VS工程文件(经测试,VS2013也完全可以使用VS2012的工程文件)。
2.1. 打开对于版本的工程文件,选择对应的配置版本,建议Realese版本。
2.2. 生成–>生成解决方案
2.3. 生成文件在\freeglut-2.8.1\lib\x86目录,有freeglut.lib, freeglut.dll.

三、安装freeglut

3.1. Header文件安装:
将\freeglut-2.8.1\include\GL 目录复制到 Microsoft Visual Studio 12.0\VC\include目录下。
3.2. 库文件安装
将freeglut.lib文件复制到Microsoft Visual Studio 12.0\VC\lib目录下。
将freeglut.dll文件复制到C:\Windows\SysWOW64 【32位系统为 “C:\Windows\System32”】目录下。

四、使用freeglut

直接包含<gl/freeglut.h>文件即可。

继续阅读 freeglut 简明安装指南 for windows

Qt 5.1 编译中处理《error C2059: 语法错误:“常量”》错误

新装了VS2013的环境,由于是64位系统并且Qt对VS2013支持不好,需要自己编译一下,但是编译过程中出现了些坑爹的问题:

error C2059: 语法错误:“常量”

报错的文件是win-math.h,代码如下:

enum {
  FP_NAN,
  FP_INFINITE,
  FP_ZERO,
  FP_SUBNORMAL,
  FP_NORMAL,
};

这段代码毫无疑问没有任何问题,但是FP_NAN这个宏可就有问题了,
FP_NAN在VS2013的标准头文件math.h被定义成了
#define _NANCODE 2
#define FP_NAN _NANCODE

结果这段代码就变成了:

enum{
  2,
  FP_INFINITE,
  FP_ZERO,
  FP_SUBNORMAL,
  FP_NORMAL,
};

其他几个枚举值也有同样的问题,解决方法有两个:
一是删除这个枚举,毕竟在math.h中已有定义。
二是使用#undef:

#undef FP_NAN
#undef FP_INFINITE
#undef FP_ZERO
#undef FP_SUBNORMAL
#undef FP_NORMAL
enum FP_MATH_{
	FP_NAN,
	FP_INFINITE,
	FP_ZERO,
	FP_SUBNORMAL,
	FP_NORMAL,
};
继续阅读 Qt 5.1 编译中处理《error C2059: 语法错误:“常量”》错误

头文件中使用static变量

static是C/C++中让人迷惑的关键字。在一个文件对变量使用static关键字,则表示这个变量的作用范围仅限于本文件。
那么对于在头文件中使用static关键字,结果会如何呢。

首先,我们要明白,C++中对于头文件的处理。每个cpp文件在编译的时候都会将头文件(*.h)进行展开,这样就导致每个生成的*.o文件中均包含使用static修饰的变量,并且这个变量对其他文件不可见。结果是出现多份的变量,每个cpp文件中都只能访问本地的变量,这是一件较为危险的使用方法。

使用如下代码验证:

/*
    staticValue.hpp
    Check if static value will be copy in different .cpp s
*/
#pragma once

static int a = 0;
/*
    funcA.cpp
*/
#include <iostream>
#include "staticValue.hpp"

int funcA()
{
    std::cout<<"[funcA]"<<" &a\t= "<<&a<<std::endl;
    std::cout<<"[funcA]"<<" &::a\t= "<<&::a<<std::endl;
    return 0;
}
/*
    funcB.cpp
*/
#include <iostream>
#include "staticValue.hpp"

int funcB()
{
    std::cout<<"[funcB]"<<" &a\t= "<<&a<<std::endl;
    std::cout<<"[funcB]"<<" &::a\t= "<<&(::a)<<std::endl;
    return 0;
}
/*
    main.cpp
*/
#include <iostream>
#include "staticValue.hpp"

extern int funcA();
extern int funcB();

int main(void)
{
    std::cout<<"[main]"<<" &a:\t= "<<&a<<std::endl;
    std::cout<<"[main]"<<" &::a:\t= "<<&::a<<std::endl;
    funcA();
    funcB();
    return 0;
}

编译执行

[email protected]:~/$ g++ -g -o static-value main.cpp funcA.cpp funcB.cpp
[email protected]:~/$ ./static-value 
[main] &a:	= 0x602198
[main] &::a:	= 0x602198
[funcA] &a	= 0x6021a0
[funcA] &::a	= 0x6021a0
[funcB] &a	= 0x6021a8
[funcB] &::a	= 0x6021a8
继续阅读 头文件中使用static变量

SQLite指南-Ubuntu篇

SQLite是一个轻量级数据库系统,SQLite基于纯C实现,具有良好的跨平台特性。SQLite使用简单,能够应付小型的软件开发应用。在开发中使用SQLite,你需要从SQLite官方网站下载源码和库文件,当然,也可以自己编译库文件。

一、下载SQLite源码

这里获得SQLite源码,解压到任意目录。

二、编译静态库文件

首先生成目标文件

gcc -c sqlite3.cc -o sqlite3.o 

从目标文件生成静态库

ar cvr libsqlite3.a sqlite3.o 

使用静态库文件生成目标 假设在SQLiteDBTest.cpp文件中使用了sqlite的相应接口,静态编译的方法如下:

gcc SQLiteDBTest.cpp libsqlite3.a -lpthread -ldl -lstdc++ -o SQLiteDBTest

其中,-lpthread -ldl参数指定了sqlite使用的线程库和dl库(参见http://www.sqlite.org/howto compile.html),lstdc++指定使用标准C++库。

三、编译动态库文件

生成动态库文件

gcc -shared -fPCI -o libsqlite3.so sqlite3.o -ldl -lpthread 

注意链接参数-ldl和-lpthread 使用动态库文件生成目标

gcc SQLiteDBTest.cpp -lsqlite3 -L. -lstdc++ -o SQLiteDBTest 

其中-L.参数指定gcc优先从当前目录搜索库文件,如果不加该行,则优先从系统目录搜索库文件。 一般开发过程中,如系统中自带sqlite库,一般不需要自行编译相应库文件。如若要使用自行编译的库文件,最好安装时将其拷贝到系统库目录中。不然的话,对于自行编译的动态库,需要指出其位置,程序才能正常运行,使用如下命令添加查找库文件位置。

export LD_LIBRARY_PATH=$(pwd)

REF:

1.How To Compile SQLite

2.gcc详解以及静态,动态库的生成 1

3.gcc详解以及静态,动态库的生成 2

继续阅读 SQLite指南-Ubuntu篇

IP设置小工具

放假闲着摆弄Qt,写了个修改IP的小工具,功能十分简单,就是修改IP设置,保存设置,读取设置。
程序的基本原理就是对netsh的包装了,用netsh修改IP的基本命令如下:

netsh interface ip set address "本地连接" static 192.168.1.2 255.255.255.0  192.168.1.1 gwmetric=1
netsh interface ip set dns name="本地连接" source=static addr=8.8.4.4
netsh interface ip add dns "本地连接" 202.112.20.131

使用ShellExecute函数来执行CMD命令。

QString strSetIP = "netsh interface ip set address "本地连接" static 192.168.1.2 255.255.255.0  192.168.1.1 gwmetric=1";
ShellExecute(NULL, L"open", L"cmd.exe", reinterpret_cast<const wchar_t *>(strSetIP.utf16()), NULL, SW_HIDE);

另外对IP地址输入的合法性,可以使用正则表达式来检查。最基本的IP形式可以理解为使用“.”分隔的四个数字,检验字符串可写为:

QRegExp strIP = "d+.d+.d+.d+";

对于进一步的检查,我们可以使用以下条件:
1.取值为0~255之间。
2.IP地址、子网掩码、网关的取值可能不为0.
则有如下表达式:

//IP可为零
QString strIPNum = "(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)";
//IP不可为零
QString strIPNum = "(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])";
QRegExp strIP = "^" + strIPNum + "." + strIPNum + "." + strIPNum + "." + strIPNum + "$";

恩,其实使用bat脚本也是挺方便的~~。

PS:NetConfig.7z
NetConfigCode.7z

继续阅读 IP设置小工具

走近WTL--GDI篇

WTL中的GDI类的封装与MFC不同,对于这些类的详细描述,可以参见WTL for MFC Programmers, Part IX – GDI Classes, Common Dialogs, and Utility Classes.
在由MFC的GDI向WTL的GDI转换过程中,有以下几个问题需要注意的:

一、OnPaint函数的处理。
在OnPaint函数的声明中,有以下的两种方法:

BEGIN_MSG_MAP_EX(MyWnd)
....
MESSAGE_HANDLER(WM_PAINT, OnPaint)
...
END_MSG_MAP()
LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);

或者是:

BEGIN_MSG_MAP_EX(MyWnd)
....
MSG_WM_PAINT(OnPaint)
...
END_MSG_MAP()
LRESULT OnPaint(HDC hdc)

实际上,这两种写法在使用中区别不大,第一种传入的四个参数都不会用到,第二种传入的hdc的值是NULL,所以这些参数都是不可以被OnPaint函数使用。那么在OnPaint中如何开始绘图呢?我们可以使用CPaintDC,如下:

LRESULT HexxagonWnd::OnPaint(HDC hdc)
{
    CPaintDC    pDC(m_hWnd);
    CDCHandle  dc(pDC.m_hDC);
...
}

这样,我们就可以方便的使用CDCHandle了。这篇文章也对这个问题做了一定的说明,大家可以参考一下:WTL7.0的一个BUG

二、如何创建双缓冲
在WTL中CDC类被CDCHandle取代,其中对CBitmap的一些操作也有了变化。

CRect lcrcClient;
GetClientRect(&lcrcClient);
CDCHandle MemDC; //首先定义一个显示设备对象
CBitmap MemBitmap;//定义一个位图对象
MemDC.CreateCompatibleDC(NULL);
MemBitmap.CreateCompatibleBitmap(dc,lcrcClient.Width(),lcrcClient.Height());
HBITMAP pOldBit=MemDC.SelectBitmap((HBITMAP)MemBitmap);

//使用MemDC绘制一些图像
....

dc.BitBlt(lcrcClient.left, lcrcClient.top,
    lcrcClient.Width(), lcrcClient.Height(),
    MemDC, lcrcClient.left, lcrcClient.top, SRCCOPY);

//绘图完成后的清理
MemBitmap.DeleteObject();
MemDC.DeleteDC();

三、SelectObject函数的变化
WTL的几个select函数都是直接使用对应类型的GDI对象,并且传入参数为HPEN、HBRUSH等类型,如下所示:

HPEN SelectPen(HPEN nPen)
HBRUSH SelectBrush(HBRUSH nBrush)
HFONT SelectFont(HFONT nFont)

一般的CPen等可以通过HPEN操作符直接从CPen转换出HPEN对象,所以保存、恢复GDI对象的代码要做如下改变:

CPen lcPen;
lcPen.CreatePen(PS_SOLID, 1, gColorGreen);
HPEN lcpOldPen = dc.SelectPen((HPEN)lcPen);

dc.SelectPen((HPEN)lcpOldPen);

当然,WTL的GDI与MFC还有一些其他的不同,大家使用时应注意。

继续阅读 走近WTL--GDI篇

COM组件调试手记

程序调试是一个优秀程序员的必备品质。这几天正好在调试一个十分郁闷的大型项目,涉及COM组件,DLL调用等方面的知识,拿出来和大家分享一下吧。

一、项目环境
1.这种调试方法适用于VS2003平台。在其他如VS2005,VS2008等也应该适用,但并未测试。
2.项目的基本结构为:两个独立的工程,一个为COM接口的调用者,我们称为Master工程;另一个为COM接口的实现部分,为一个DLL,我们暂且称为ComImpl.

二、调试前提
1.必须拥有Master和ComImpl工程的源代码。
2.将由源码生成的exe、dll、pdb文件等必须要在同一文件夹下。
3.将VS2003加入到DEP保护的排除列表中,这一步十分重要,否则一旦开始调试,VS2003的IDE就会挂掉。

三、调试步骤
1.使用VS2003打开Master工程,在调用COM接口的代码处地方设置断点,启动调试。
2.等程序在调用COM接口的代码处被中断时,打开VS2003的Debug菜单下Processes对话框,attach到Dllhost.exe,关闭对话框。
3.在Master工程中打开ComImpl工程的源码,设置断点。
4.按F5或F10执行,直到ComImpl中的断点被捕获。

四、总结
实际上整个调试过程还算比较简单的,一般引起无法调试的原因无非是调试前提中的三点没有满足,特别是第三点一般很难有人会想到。

继续阅读 COM组件调试手记