面对现实,超越自己
逆水行舟,不进则退
posts - 269,comments - 32,trackbacks - 0

很早前就想写点总结将编程中遇到的各种错误刨根挖底地罗列出来。但是因为这些错误(VC中开调试器遇到的各种错误对话框)都是随机性的,真正想总结的时候又不想不起来有哪些错误。恰好最近运气比较背,各种错误都被我遇遍了,于是恰好有机会做个总结。

这里所说的VC下的错误对话框时指在VC中开调试器运行程序时,IDE弹出的对话框。

1.不是错误的错误:断言 .

将断言视为错误其实有点可笑,但是因为有些同学甚至不知道这个,所以我稍微提一下。断言对话框大致上类似于:

 

断言对话框是由assert引起的,在对话框上通常会给出表达式,例如assert( 0 ); 弹出对话框时就会将0这个表达式显示出来(Expression:0)。关于assert的具体信息建议自己google。这里稍微提一下一个技巧:有时候为了让assert提供更多的信息,我们可以这样写一个assert:

assert( expression && "Function : invalid argument!" );

因为字符串被用在布尔表达式中时,始终为true,不会妨碍对expression的判断,当断言发生时(expression为false) 时,断言对话框上就会显示这个字符串,从而方便我们调试。

要解决这个问题,首先要确定断言发生的位置,如果是你自己设置的断言被引发,就很好解决,如果是系统内部的函数产生的,那么一般是因为你传入的函数参数无效引起。

2.内存相关:最简单的非法访问:

C、C++程序中经常误用无效的指针,从而大致各种各样的非法内存访问(写/读)。最简单的情况类似于:

 

这样的情况由类似以下代码引起:

char *p = 0;

*p = 'a';

当你看到类似于“写入位置XXXX时发生访问冲突“时,那么你大致可以断定,你的程序在某个地方访问到非法内存。开调试器对调用堆栈进行跟踪即可找出错误。

3.内存相关:不小心的栈上数组越界:

当你写下类似以下的代码时:

char str[3];

strcpy( str, "abc" );

就将看到如下的对话框:

 

对话框大致的意思就是说str周围的栈被破坏了,因为str本身就被放在栈上,所以strcpy(str,"abc")多写入的'\0'就写到非法的栈区域。看到这样的对话框可以根据调用堆栈定位到错误发生的函数,然后检查此函数内部定义的数组访问,即可解决问题。

4.内存相关:不小心的堆上数组越界:
并不是每次数组越界都会得到上面所描述的错误,当数组是在堆上分配时,情况就变得隐秘得多:

char *str = new char [2];

strcpy( str, "ab" ); //执行到这里时并不见得会崩溃

delete [] str;//但是到这里时就肯定会崩溃

以上代码导致的错误对话框还要诡异些:

 

似乎不同的DAMAGE对应的错误号(这里是47)都不一样,因为这里的错误发生在delete,而delete跟new很可能在不同的地方,所以这个错误调试起来不是那么容易,很多时候只能靠经验。

当看到类似的对话框时,根据调用堆栈跟到delete时,你就可以大致怀疑堆上数组越界。

5.调用相关:函数调用约定带来的错误:

这是所有我这里描述的错误中最诡异的一种,先看下对话框大致的样子:

 

对话框大致的意思就是说(没开调试器时对话框样式可能不一样),通过函数指针调用某个函数时,函数指针的类型(函数原型)可能与函数指针指向的函数的类型不一样。这里的类型不一致主要是调用约定(call conversation)不一样。如果函数类型(参数个数,返回值)不一样,一般不会出错。

调用约定是指调用一个函数时,函数参数的压入顺序、谁来清理栈的内容等。例如默认的C、C++调用约定__cdecl,对于函数的参数是从右往左压入。而__stdcall(WIN API的调用约定)则是从左向右压。我这里所说的函数类型不一样,就是指一个函数是使用__cdecl,还是__stdcall。例如以下代码:

#include <iostream> 

void __stdcall show( const char *str )

{

}
 

void __stdcall show2()

{

}
 

int main()

{

typedef
void (*Func)( const char *);

void *p = show;

Func my_func
= (Func) p;

my_func(
"kevin" );

return 0;

}
 

 因为Func默认地被处理为__cdecl,而show是__stdcall的,所以当通过函数指针my_func时,就导致了以上对话框的出现。但是当p指向show2时,又不会出错,这是因为show2没有参数,不同的调用约定不影响这个规则。

6.异常相关:默认终止程序

当我们使用C++库时,因为库本身可能会抛出C++异常,如果你不捕获这个异常,那么C++默认就会调用abort(或者exit)函数终止程序。例如:

void test()
{
   
throw std::exception( "some exceptions" );
}

 当你调用test函数时,如果不catch这个异常,开调试器就会得到类似的错误对话框:

 

而如果不开调试器,则会得到:

 

当你看到类似于“This application has requested the Runtime to terminate it…”之类的字眼时,那就表明程序调用了abort(或exit)函数,导致程序异常终止。其实这个错误只要开调试器,一般可以准确定位错误的发生点。

7.VC运行时检查-未初始化变量

VC的调试器会对代码进行运行时检查,这可能会导致VC弹出对你看上去正确的代码。这也许不是一个错误。例如:

int test_var;

if( test_var == -1 )
{
    test_var = 0;
}

test_var没有初始化就进行if判断,当运行以上代码开调试器时,就会得到如下对话框:

 

8.破坏的堆

VC对于在堆上分配的内存都做了记录,我想这主要用于free释放内存时做归还处理。

char *p = (char*) malloc( 100 );
p += 10;
free( p );

当执行以上代码时,因为p的值已经改变,提交到free的指针值变化,VC就会给出以下错误提示:



本文转自:http://www.cppblog.com/kevinlynx/archive/2008/04/24/47998.html

posted @ 2012-09-14 14:18 王海光 阅读(559) | 评论 (0)编辑 收藏

有一天有个同事在通过vld调试一个内存泄漏问题,折腾了很久然后找到我。我瞥了一眼他的代码,发现问题和我曾经遇到的一模一样:

 1 class Base {
 2 public:
 3     ~Base();
 4 };
 5 
 6 class Derived : public Base {
 7 privated:
 8     std::vector<int> m_data;    };
 9 
10 Base *obj = new Derived();
11 delete obj;

当然,实际代码比这个复杂得多(这也是导致从发现问题到找到问题耗费大量时间的原因)。vld在报内存泄漏时,当然报的位置是new的地方。这个同事检查了这个对象的整个生命周期,确定他正确地释放了这个对象。

问题的关键就在于:Base类的析构函数不是virtual。因为不是virtual,所以在对一个Base类型的指针进行delete时,就不会调用到派生类Derived的析构函数。而派生类里的析构函数会用于析构其内部的子对象,也就是这里的m_data。这样,就造成了内存泄漏。

这其实是一个很低级的失误。但毫不客气地说C++中有很多这种少个关键字或者代码位置不对就会造成另一个结果的例子。事实上,针对这些悲剧也有很多书提出一些准则来让大家去无脑遵守。例如针对这个例子,我就记得曾有书说,只要你觉得你的类会被继承,那么最好给析构函数加上virtual。

posted @ 2012-09-14 13:54 王海光 阅读(406) | 评论 (0)编辑 收藏
     摘要: win7下得到操作系统版本错误( GetVersionEx) 有一段得到操作系统版本的代码,用的是 GetVersionEx 方法,在当前机器的硬盘上运行,得到的操作系统版本是win7。奇怪的是,在U盘上运行,得到的操作系统版本是XP。 直接说原因:  不知道什么时候,系统在注册表中设置了U盘exe的XP兼容项(在兼容性助手弹出时,选择重新启动或者重新安装时,系统会在H...  阅读全文
posted @ 2012-09-14 11:23 王海光 阅读(4817) | 评论 (0)编辑 收藏
     摘要: CListCtrl使用技巧 以下未经说明,listctrl默认view 风格为report 1. CListCtrl 风格       LVS_ICON: 为每个item显示大图标      LVS_SMALLICON: 为每个item显示小图标  &nbs...  阅读全文
posted @ 2012-09-13 14:31 王海光 阅读(2209) | 评论 (0)编辑 收藏
响应WM_CTLCOLOR消息
WM_CTLCOLOR消息的响应函数.此函数的原型:
  afx_msg HBRUSH OnCtlColor(CDC *pDC,CWnd *pWnd,UINT nCtlColor);
  参数nCtlColor用于指定控件的类型,可以是:
  .CTLCOLOR_BTN       按钮控件
  .CTLCOLOR_DLG       对话框
  .CTLCOLOR_EDIT      编辑框
  .CTLCOLOR_LISTBOX   列表控件
  .CTLCOLOR_MSGBOX    消息控件
  .CTLCOLOR_SCROLLBAR 滚动条控件
  .CTLCOLOR_STATIC    静态控件


MSDN中信息:

The framework calls this member function when a child control is about to be drawn.

1 afx_msg HBRUSH OnCtlColor(
2    CDC* pDC,
3    CWnd* pWnd,
4    UINT nCtlColor 
5 );

Parameters

pDC

Contains a pointer to the display context for the child window. May be temporary.

pWnd

Contains a pointer to the control asking for the color. May be temporary.

nCtlColor

Contains one of the following values, specifying the type of control:

  • CTLCOLOR_BTN   Button control

  • CTLCOLOR_DLG   Dialog box

  • CTLCOLOR_EDIT   Edit control

  • CTLCOLOR_LISTBOX   List-box control

  • CTLCOLOR_MSGBOX   Message box

  • CTLCOLOR_SCROLLBAR   Scroll-bar control

  • CTLCOLOR_STATIC   Static control

Return Value

OnCtlColor must return a handle to the brush that is to be used for painting the control background.

Example
 1 // This OnCtlColor handler will change the color of a static control
 2 // with the ID of IDC_MYSTATIC. The code assumes that the CPenWidthsDlg
 3 // class has an initialized and created CBrush member named m_brush.
 4 // The control will be painted with red text and a background
 5 // color of m_brush.
 6 HBRUSH CPenWidthsDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
 7 {
 8    // Call the base class implementation first! Otherwise, it may
 9    // undo what we're trying to accomplish here.
10    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
11 
12    // Are we painting the IDC_MYSTATIC control? We can use
13    // CWnd::GetDlgCtrlID() to perform the most efficient test.
14    if (pWnd->GetDlgCtrlID() == IDC_MYSTATIC)
15    {
16       // Set the text color to red
17       pDC->SetTextColor(RGB(25500));
18 
19       // Set the background mode for text to transparent 
20       // so background will show thru.
21       pDC->SetBkMode(TRANSPARENT);
22 
23       // Return handle to our CBrush object
24       hbr = m_brush;
25    }
26 
27    return hbr;
28 }
 

Requirements

Header: afxwin.h


posted @ 2012-09-12 16:34 王海光 阅读(973) | 评论 (0)编辑 收藏
    复杂的东西写多了,如今写点简单的好了。由于功能上的需要,Vczh Library++3.0被我搞得很离谱。为了开发维护的遍历、减少粗心犯下的错误以及增强单元测试、回归测试和测试工具,因此记录下一些开发上的小技巧,以便抛砖引玉,造福他人。欢迎高手来喷,菜鸟膜拜。

    之前的文章讲了指针和内存的一些问题,今天说一下单元测试的问题。如果在团队里面没有对单元测试的框架有要求的话,其实我们可以使用一个最简单的方法来搭建在IDE里面运行的单元测试框架,整个框架只需十几行代码。我们先来考虑一下功能最少的单元测试框架需要完成什么样的内容。首先我们要运行一个一个的测试用例,其次在一个测试用例里面我们要检查一些条件是否成立。举个例子,我们写一个函数将两个字符串连接起来,一般来说要进行下面的测试:
 1 #include "MyUnitTestFramework.h"//等一下我们会展示一下如何用最少的代码完成这个头文件的内容
 2 #include ""
 3 
 4 TEST_CASE(StringConcat)
 5 {
 6   TEST_ASSERT(concat("a""b")=="ab");
 7   TEST_ASSERT(concat("a""")=="a");
 8   TEST_ASSERT(concat("""b")=="b");
 9   TEST_ASSERT(concat("""")=="");
10   .
11 }
12 
13 int wmain()
14 {
15   return 0;
16 }

    如果我们的单元测试框架可以这么写,那显然做起什么事情来都会方便很多,而且不需要向一些其他的测试框架一样注册一大堆东西,或者是写一大堆配置函数。当然这次我们只做功能最少的测试框架,这个框架除了运行测试以外,不会有其他功能,譬如选择哪些测试可以运行啦,还是在出错的时候log一些什么啦之类。之所以要在IDE里面运行,是因为我们如果做到TEST_ASSERT中出现false的话,立刻在该行崩溃,那么IDE就会帮你定位到出错的TEST_ASSERT中去,然后给你显示所有的上下文信息,譬如说callstack啦什么的。友好的工具不用简直对不起自己啊,干吗非得把单元测试做得那么复杂捏,凡是单元测试,总是要全部运行通过才能提交代码的。

    那么我们来看看上面的单元测试的代码。首先写了TEST_CASE的那个地方,大括号里面的代码会自动运行。其次TEST_ASSERT会在表达式是false的时候崩溃。先从简单的入手吧。如何制造崩溃呢?最简单的办法就是抛异常:
1 #define TEST_ASSERT(e) do(if(!(e))throw "今晚没饭吃。";}while(0)

    这里面有两个要注意的地方。首先e要加上小括号,不然取反操作符就有可能做出错误的行为。譬如说当e是a+b==c的时候,加了小括号就变成if(!(a+b==c))...,没有加小括号就变成if(!a+b==c)...,意思就完全变了。第二个主意的地方是我使用do{...}while(0)把语句包围起来了。这样做的好处是可以在任何时候TEST_ASSERT(e)都像一个语句。譬如我们可能这么写:
1 if(a)
2   TEST_ASSERT(x1);
3 else if(b)
4 {
5   TEST_ASSERT(x2);
6   TEST_ASSERT(x3);
7 }

    如果没有do{...}while(0)包围起来,这个else就会被绑定到宏里面的那个if,你的代码就被偷偷改掉了。

    那么现在剩下TEST_CASE(x){y}了。什么东西可以在main函数外面自动运行呢?这个我想熟悉C++的人都会知道,就是全局变量的构造函数啦。所以TEST_CASE(x){y}那个大括号里面的y只能在全局变量的构造函数里面调用。但是我们知道写一个类的时候,构造函数的大括号写完了,后面还有类的大括号,全局变量的名称,和最终的一个分号。为了把这些去掉,那么显然{y}应该属于一个普通的函数。那么全局变量如何能够使用这个函数呢?方法很简单,把函数前置声明一下就行了:
 1 #define TEST_CASE(NAME)                                            \
 2         extern void TESTCASE_##NAME();                             \
 3         namespace vl_unittest_executors                            \
 4         {                                                          \
 5             class TESTCASE_RUNNER_##NAME                           \
 6             {                                                      \
 7             public:                                                \
 8                 TESTCASE_RUNNER_##NAME()                           \
 9                 {                                                  \
10                     TESTCASE_##NAME();                             \
11                 }                                                  \
12             } TESTCASE_RUNNER_##NAME##_INSTANCE;                   \
13         }                                                          \
14         void TESTCASE_##NAME()

    那我们来看看TEST_CASE(x){y}究竟会被翻译成什么代码:
 1 extern void TESTCASE_x();
 2 namespace vl_unittest_executors
 3 {
 4     class TESTCASE_RUNNER_x
 5     {
 6     public:
 7         TESTCASE_RUNNER_x()
 8         {
 9             TESTCASE_x();
10         }
11     } TESTCASE_RUNNER_x_INSTANCE;
12 }
13 void TESTCASE_x(){y}

    到了这里是不是很清楚了捏,首先在main函数运行之前TESTCASE_RUNNER_x_INSTANCE变量会初始化,然后调用TESTCASE_RUNNER_x的构造函数,最后运行函数TESTCASE_x,该函数的内容显然就是{y}了。这里还能学到宏是如何连接两个名字成为一个名字,和如何写多行的宏的。

    于是MyUnittestFramework.h就包含这两个宏,其他啥都没有,是不是很方便呢?打开Visual C++,建立一个工程,引用这个头文件,然后写你的单元测试,最后F5就运行了,多方便啊,啊哈哈哈。

    这里需要注意一点,那些单元测试的顺序是不受到保证的,特别是你使用了多个cpp文件的情况下。于是你在使用这个测试框架的同时,会被迫保证执行一次单元测试不会对你的全局状态带来什么副作用,以便两个测试用例交换顺序执行的时候仍然能稳定地产生相同的结果。这对你写单元测试有帮助,而且为了让你的代码能够被这么测试,你的代码也会写的有条理,不会依赖全局状态,真是一举两得也。而且说不定单元测试用例比你的全局变量的初始化还先执行呢,因此为了使用这个测试框架,你将会不得不把你的全局变量隐藏在一个cpp里面,而暴露出随时可以被调用的一组函数出来。这样也可以让你的代码在使用全局状态的时候更加安全。

    今天就讲到这里了。下一篇要写什么我还没想好,到时候再说吧。

本文转自:http://www.cppblog.com/vczh/archive/2010/06/27/118829.html
posted @ 2012-09-12 14:54 王海光 阅读(765) | 评论 (0)编辑 收藏
     摘要: C++实用技巧(一)     复杂的东西写多了,如今写点简单的好了。由于功能上的需要,Vczh Library++3.0被我搞得很离谱。为了开发维护的遍历、减少粗心犯下的错误以及增强单元测试、回归测试和测试工具,因此记录下一些开发上的小技巧,以便抛砖引玉,造福他人。欢迎高手来喷,菜鸟膜拜。    C++实谓各种语言中的软肋,功能强大,陷阱...  阅读全文
posted @ 2012-09-12 14:51 王海光 阅读(478) | 评论 (0)编辑 收藏
在高效C++编程中看到一个不错的内存池实现方案,这里共享下,大家看看有什么不足。
代码很简单,如下:
template<typename T>
class CMemoryPool
{
    public:
        enum { EXPANSION_SIZE = 32};

        CMemoryPool(unsigned int nItemCount = EXPANSION_SIZE)
        {
            ExpandFreeList(nItemCount);
        }
        
        ~CMemoryPool()
        {
            //free all memory in the list
            CMemoryPool<T>* pNext = NULL;
            for(pNext = m_pFreeList; pNext != NULL; pNext = m_pFreeList)
            {
                m_pFreeList = m_pFreeList->m_pFreeList;
                delete [](char*)pNext;
            }
        }

        void* Alloc(unsigned int /*size*/)
        {
            if(m_pFreeList == NULL)
            {
                ExpandFreeList();
            }
            
            //get free memory from head
            CMemoryPool<T>* pHead = m_pFreeList;
            m_pFreeList = m_pFreeList->m_pFreeList;
            return pHead;
        }

        void Free(void* p)
        {
            //push the free memory back to list
            CMemoryPool<T>* pHead = static_cast<CMemoryPool<T>*>(p);
            pHead->m_pFreeList = m_pFreeList;
            m_pFreeList = pHead;
        }

    protected:
        //allocate memory and push to the list
        void ExpandFreeList(unsigned nItemCount = EXPANSION_SIZE)
        {
            unsigned int nSize = sizeof(T) > sizeof(CMemoryPool<T>*) ? sizeof(T) : sizeof(CMemoryPool<T>*);
            CMemoryPool<T>* pLastItem = static_cast<CMemoryPool<T>*>(static_cast<void*>(new char[nSize]));
            m_pFreeList = pLastItem;
            for(int i=0; i<nItemCount-1; ++i)
            {
                pLastItem->m_pFreeList = static_cast<CMemoryPool<T>*>(static_cast<void*>(new char[nSize]));
                pLastItem = pLastItem->m_pFreeList;
            }

            pLastItem->m_pFreeList = NULL;
        }

    private:
        CMemoryPool<T>* m_pFreeList;
};

它的实现思想就是每次从List的头上取内存, 如果取不到则重新分配一定数量; 用完后把内存放回List头部,这样的话效率很高,因为每次List上可以取到的话,肯定是空闲的内存。

当然上面的代码只是针对单线程的,要支持多线程的话也很简单,外面加一层就可以了,
代码如下:
class CCriticalSection
{
public:
    CCriticalSection()
    {
        InitializeCriticalSection(&m_cs);
    }

    ~CCriticalSection()
    {
        DeleteCriticalSection(&m_cs);
    }

    void Lock()
    {
        EnterCriticalSection(&m_cs); 
    }

    void Unlock()
    {
        LeaveCriticalSection(&m_cs);
    }

protected:
    CRITICAL_SECTION m_cs;
};

template<typename POOLTYPE, typename LOCKTYPE>
class CMTMemoryPool
{
    public:
        void* Alloc(unsigned int size)
        {
            void* p = NULL;
            m_lock.Lock();
            p = m_pool.Alloc(size);
            m_lock.Unlock();

            return p;
        }

        void Free(void* p)
        {
            m_lock.Lock();
            m_pool.Free(p);
            m_lock.Unlock();    
        }

    private:
        POOLTYPE m_pool;
        LOCKTYPE m_lock;
};

这是我的测试代码:
#include <iostream>
#include <windows.h>

using namespace std;

#include "MemoryPool.h"
#include "MTMemoryPool.h"

class CTest
{
public:
    int m_n;
    int m_n1;

    voidoperator new(size_t size)
    {
        void* p = s_pool->Alloc(size);
        return p;
    }

    void operator delete(void* p, size_t size)
    {
        s_pool->Free(p);
    }

    static void NewPool()
    {
        //s_pool = new CMemoryPool<CTest>;
        s_pool = new CMTMemoryPool<CMemoryPool<CTest>, CCriticalSection>;
    }

    static void DeletePool()
    {
        delete s_pool;
        s_pool = NULL;
    }
    
    //static CMemoryPool<CTest>* s_pool;
    static CMTMemoryPool<CMemoryPool<CTest>, CCriticalSection>* s_pool;
};

//CMemoryPool<CTest>* CTest::s_pool = NULL;
CMTMemoryPool<CMemoryPool<CTest>, CCriticalSection>* CTest::s_pool = NULL;

void testFun()
{
    int i;
    const int nLoop = 10;
    const int nCount = 10000;
    
    for(int j = 0; j<nLoop; ++j)
    {
        typedef CTest* LPTest;
        LPTest arData[nCount];
        for(i=0;i <nCount; ++i)
        {
            arData[i] = new CTest;
        }

        for(i=0;i <nCount; ++i)
        {
            delete arData[i];
        }
    }
}

int main(int argc, char* argv[])
{
    {
        unsigned int dwStartTickCount = GetTickCount();

        CTest::NewPool();

        testFun();
        
        CTest::DeletePool();
        
        cout << "total cost" << GetTickCount() - dwStartTickCount << endl;
    }


    system("pause");

    return 0;
}
在我机器上测试结果比系统默认的CRT实现高效N倍。

本文转自:http://www.cppblog.com/weiym/archive/2012/05/05/173785.aspx
posted @ 2012-09-11 11:30 王海光 阅读(534) | 评论 (0)编辑 收藏
1、Callback方式

Callback的本质是设置一个函数指针进去,然后在需要需要触发某个事件时调用该方法, 比如Windows的窗口消息处理函数就是这种类型。

比如下面的示例代码,我们在Download完成时需要触发一个通知外面的事件:
typedef void (__stdcall *DownloadCallback)(const char* pURL, bool bOK);
void DownloadFile(const char* pURL, DownloadCallback callback)
{
    cout << "downloading: " << pURL << "" << endl;
    callback(pURL, true);
}
void __stdcall OnDownloadFinished(const char* pURL, bool bOK)
{
    cout << "OnDownloadFinished, URL:" << pURL << "    status:" << bOK << endl;
}

2、Sink方式

Sink的本质是你按照对方要求实现一个C++接口,然后把你实现的接口设置给对方,对方需要触发事件时调用该接口, COM中连接点就是居于这种方式。

上面下载文件的需求,如果用Sink实现,代码如下:
class IDownloadSink
{
public:
    virtual void OnDownloadFinished(const char* pURL, bool bOK) = 0;
};
class CMyDownloader
{
public:
    CMyDownloader(IDownloadSink* pSink)
        :m_pSink(pSink)
    {
    }

    void DownloadFile(const char* pURL)
    {
        cout << "downloading: " << pURL << "" << endl;
        if(m_pSink != NULL)
        {
            m_pSink->OnDownloadFinished(pURL, true);
        }
    }

private:
    IDownloadSink* m_pSink;
};

class CMyFile: public IDownloadSink
{
public:
    void download()
    {
        CMyDownloader downloader(this);
        downloader.DownloadFile("www.baidu.com");
    }

    virtual void OnDownloadFinished(const char* pURL, bool bOK)
    {
        cout << "OnDownloadFinished, URL:" << pURL << "    status:" << bOK << endl;
    }
};

3、Delegate方式

Delegate的本质是设置成员函数指针给对方,然后让对方在需要触发事件时调用。
C#中用Delegate的方式实现Event,让C++程序员很是羡慕,C++中因为语言本身的关系,要实现Delegate还是很麻烦的。
上面的例子我们用Delegate的方式实现如下: 
class CDownloadDelegateBase
{
public:
    virtual void Fire(const char* pURL, bool bOK) = 0;
};

template<typename O, typename T>
class CDownloadDelegate: public CDownloadDelegateBase
{
    typedef void (T::*Fun)(const char*, bool);
public:
    CDownloadDelegate(O* pObj = NULL, Fun pFun = NULL)
        :m_pFun(pFun), m_pObj(pObj)
    {
    }
    
    virtual void Fire(const char* pURL, bool bOK)
    {
        if(m_pFun != NULL
            && m_pObj != NULL)
        {
            (m_pObj->*m_pFun)(pURL, bOK);
        }
    }

private:
    Fun m_pFun;
    O* m_pObj;
};

template<typename O, typename T>
CDownloadDelegate<O,T>* MakeDelegate(O* pObject, void (T::*pFun)(const char* pURL, bool))
{
    return new CDownloadDelegate<O, T>(pObject, pFun);
}

class CDownloadEvent
{
public:
    ~CDownloadEvent()
    {
        vector<CDownloadDelegateBase*>::iterator itr = m_arDelegates.begin();
        while (itr != m_arDelegates.end())
        {
            delete *itr;
            ++itr;
        }
        m_arDelegates.clear();
    }

    void operator += (CDownloadDelegateBase* p)
    {
        m_arDelegates.push_back(p);
    }

    void operator -= (CDownloadDelegateBase* p)
    {
        ITR itr = remove(m_arDelegates.begin(), m_arDelegates.end(), p);

        ITR itrTemp = itr;
        while (itrTemp != m_arDelegates.end())
        {
            delete *itr;
            ++itr;
        }
        m_arDelegates.erase(itr, m_arDelegates.end());
    }

    void operator()(const char* pURL, bool bOK)
    {
        ITR itrTemp = m_arDelegates.begin();
        while (itrTemp != m_arDelegates.end())
        {
            (*itrTemp)->Fire(pURL, bOK);
            ++itrTemp;
        }
    }

private:
    vector<CDownloadDelegateBase*> m_arDelegates;
    typedef vector<CDownloadDelegateBase*>::iterator ITR;
};


class CMyDownloaderEx
{
public:
    void DownloadFile(const char* pURL)
    {
        cout << "downloading: " << pURL << "" << endl;
        downloadEvent(pURL, true);
    }

    CDownloadEvent downloadEvent;
};

class CMyFileEx
{
public:
    void download()
    {
        CMyDownloaderEx downloader;
        downloader.downloadEvent += MakeDelegate(this, &CMyFileEx::OnDownloadFinished);
        downloader.DownloadFile("www.baidu.com");
    }

    virtual void OnDownloadFinished(const char* pURL, bool bOK)
    {
        cout << "OnDownloadFinished, URL:" << pURL << "    status:" << bOK << endl;
    }
};

可以看到Delegate的方式代码量比上面其他2种方式大多了,并且我们上面是固定参数数量和类型的实现方式,如果要实现可变参数,要更加麻烦的多。
可变参数的方式可以参考这2种实现:
Yet Another C#-style Delegate Class in Standard C++
Member Function Pointers and the Fastest Possible C++ Delegates


我们可以用下面的代码测试我们上面的实现:
int _tmain(int argc, _TCHAR* argv[])
{

    DownloadFile("www.baidu.com", OnDownloadFinished);

    CMyFile f1;
    f1.download();

    CMyFileEx ff;
    ff.download();

    system("pause");

    return 0;
}


最后简单比较下上面3种实现回调的方法:
第一种Callback的方法是面向过程的,使用简单而且灵活,正如C语言本身。
第二种Sink的方法是面向对象的,在C++里使用较多, 可以在一个Sink里封装一组回调接口,适用于一系列比较固定的回调事件。
第三种Delegate的方法也是面向对象的,和Sink封装一组接口不同,Delegate的封装是以函数为单位,粒度比Sink更小更灵活。 

你更倾向于用哪种方式来实现回调?

本文转自:http://www.cppblog.com/weiym/archive/2012/08/28/188515.html
posted @ 2012-09-11 10:43 王海光 阅读(443) | 评论 (0)编辑 收藏
memmove、memcpy和memccpy三个函数都是内存的拷贝,从一个缓冲区拷贝到另一个缓冲区。
memmove(void *dest,void*src,int count)
memcpy(void *dest,void *src,int count)
memccpy(void*dest,void*src,int ch,int count)

表头文件: #include <string.h>
定义函数: void *memcpy(void *dest, const void *src, size_t n)
函数说明: memcpy()用来拷贝src所指的内存内容前n个字节到dest所指的内存地址上。与strcpy()不同的是,memcpy()会完整的复制n个字节,不会因为遇到字符串结束'\0'而结束
返回值:   返回指向dest的指针

表头文件: #include <string.h>
定义函数: void *memccpy(void *dest, const void *src, int c, size_t n);
函数说明: memccpy()用来拷贝src所指的内存内容前n个字节到dest所指的地址上。与memcpy()不同的是,memccpy()如果在src中遇到某个特定值(int c)立即停止复制。
返回值:   返回指向dest中值为c的下一个字节指针。返回值为0表示在src所指内存前n个字节中没有值为c的字节。

表头文件: #include <string.h>
定义函数: void *memmove(void *dest, const void *src, size_t n);
函数说明:memmove()是从一个缓冲区移动到另一个缓冲区中。 
返回值:   返回指向dest指针。

当dest <= src-count 或dest >= src+count时,以上三个函数均不会产生覆盖问题,即源数据不会被更改。
若不在以上范围内,则源数据会被更改。

如:
char a[]={'a','b'};
char b[]={'c','d','e','f','g','h'};
memmove(a,b,sizeof(b));
或是直接char *p=b+2;memmove(p,b,sizeof(b));
输出数据会发现b中数据输出已被更改。
发现即使a数组指向的空间不够存储数据,也能够移动成功。
原因|dest - src |<count

如果在使用这些函数时,分配给足够的空间,然后再使用就不会出现覆盖问题。也就是说如果外部分配给的空间不足以存储要拷贝的数据时,就有可能出现源数据被覆盖更改的问题。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void main(void)
{
 int i=0;
    char a[9]={'a','b','c','d','e','f','g','h','\0'};
 char p[2]={'q','w'};//或char *p=a+2;
 memmove(p,a,sizeof(a));
    puts(a);
 printf("_____________________________________________\n");
 puts(p);
 printf("_____________________________________________\n");
  for(i =0;i<10;i++)
   printf("%c %d \n",*(a+i),a+i);
 printf("_____________________________________________\n");
 for(i =0;i<8;i++)
   printf("%c %d \n",*(p+i),p+i); 
}
观察输出结果。
把memmove(p,a,sizeof(a));改为memcpy(p,a,sizeof(a));或memccpy(p,a,'e',sizeof(a));再观察输出结果。
可以看出在目的存储空间不足时,便会出现源数据被覆盖改变的问题。
如果目的存储空间分配足够的空间,则便不会出现覆盖问题。

本文转自:http://www.cppblog.com/kang/archive/2009/04/05/78984.html
posted @ 2012-09-11 10:28 王海光 阅读(538) | 评论 (0)编辑 收藏
仅列出标题
共27页: First 13 14 15 16 17 18 19 20 21 Last