C++ 中 Bool functor 的优化

阅读更多关于《C++ 中 Bool functor 的优化》

原以为足够现代的编译器的优化能力很强,看来我是高估了。GCC 没测过,VC 2008 刚刚被证实了。

    pair<int,int> x(rand(), rand()), y(rand(), rand());

   for (int i = 0; i < 1000; ++i)

   {

      if (x < y)

         x.first += y.second, y.second -= x.second;

   }

 

// pair.< 的定义

template<class _Ty1, class _Ty2> inline

bool operator<(const pair<_Ty1, _Ty2>& _Left,

               const pair<_Ty1, _Ty2>& _Right)

{  // test if _Left < _Right for pairs

   return (_Left.first < _Right.first ||

      !(_Right.first < _Left.first) && _Left.second < _Right.second);

}

主要是看其中的 if (x < y) 会被优化成什么样子。我原估计的是会在 inline 展开之后,将函数的返回值直接映射到true/false分支,没想到让我大失所望:

 

; 93   :  {
; 94   :   if (x < y)

  003d2 8b 45 e4  mov  eax, DWORD PTR _x$[ebp]
  003d5 3b 45 ec  cmp  eax, DWORD PTR _y$[ebp]
  003d8 7c 1c   jl  SHORT $LN71@TestBoolOp
  003da 8b 4d ec  mov  ecx, DWORD PTR _y$[ebp]
  003dd 3b 4d e4  cmp  ecx, DWORD PTR _x$[ebp]
  003e0 7c 08   jl  SHORT $LN70@TestBoolOp
  003e2 8b 55 e8  mov  edx, DWORD PTR _x$[ebp+4]
  003e5 3b 55 f0  cmp  edx, DWORD PTR _y$[ebp+4]
  003e8 7c 0c   jl  SHORT $LN71@TestBoolOp
$LN70@TestBoolOp:
  003ea c7 85 40 ff ff
 ff 00 00 00 00  mov  DWORD PTR tv84[ebp], 0
  003f4 eb 0a   jmp  SHORT
$LN68@TestBoolOp
$LN71@TestBoolOp:
  003f6 c7 85 40 ff ff
 ff 01 00 00 00  mov  DWORD PTR tv84[ebp], 1
$LN68@TestBoolOp:
  00400 0f b6 85 40 ff
 ff ff   movzx  eax, BYTE PTR tv84[ebp]
  00407 85 c0   test  eax, eax
  00409 74 12   je  SHORT
$LN1@TestBoolOp

; 95   :    x.first += y.second, y.second -= x.second;

  0040b 8b 4d e4  mov  ecx, DWORD PTR _x$[ebp]
  0040e 03 4d f0  add  ecx, DWORD PTR _y$[ebp+4]
  00411 89 4d e4  mov  DWORD PTR _x$[ebp], ecx
  00414 8b 55 f0  mov  edx, DWORD PTR _y$[ebp+4]
  00417 2b 55 e8  sub  edx, DWORD PTR _x$[ebp+4]
  0041a 89 55 f0  mov  DWORD PTR _y$[ebp+4], edx
$LN1@TestBoolOp:

; 96   :  }

  0041d eb a1   jmp  SHORT $LN3@TestBoolOp
$LN2@TestBoolOp:

我原乐观地以为编译器的代码不会出现紫色背景的代码,而红色背景的跳转会直接到黄色处,绿色背景的跳转会跳过黄色。是我对编译器要求太高了吗?可是,这样functor在排序、查找等应用中是随处可见的,要浪费多少CPU 啊!真的是硬件水平高了,软件就可以随意挥霍了吗?

与其这样,类似 std::sort 这样的函数还不如不要 inline Compare Functor,直接使用 qsort 算了,还不用那么多的代码膨胀。感觉这样的代码膨胀带来的效益太少了,好像某权威测试对 std::sort 和 qsort  的对简单数据的排序对比说明它仅比 qsort 快 20%~30%。可是在一个典型应用中它带来的代码膨胀恐怕 2000%~3000%都有吧。

现在用习惯了 C++ 的 Template,不用它就感觉不舒服。但是经常一个小规模的 C++ 程序编译出的执行程序就有2~3M,这还是优化过,去掉所有调试信息的。真不知道如何办好!

gcc 一个恶心的 bug

阅读更多关于《gcc 一个恶心的 bug》

我的测试项目下面有 4 个 .cpp 文件, 测试我写的模版(在另一些 .h 中)。

测试的编译选项主要是 -g3 -O0 ,无优化

当我改变了模版头文件,因为用到的测试代码在 main.cpp 中(包含了模版头文件),我把 main.o 删掉,重新编译,结果模版头文件的修改就是不能生效,跟没改一样,搞了很多次,都是不对,我一直以为是自己的代码有问题。在我快要疯掉时,我 make clean 所有 .o 删掉,再编译,竟然好了!

我操!我又没有使用预编译头,所有测试代码都在 main.cpp 中,它竟然把 main.cpp 代码编译到了其它 .o 中!

千万注意,不要 hack std::string

阅读更多关于《千万注意,不要 hack std::string》

前段时间被一个bug折磨了两个星期,最后发现竟然是如此一个陷阱——我为了减少内存用量并且减少一次内存拷贝,直接通过string.data()修改了string的内部表示。这与其说是一个陷阱,不如说是我自己给自己造了一个陷阱然后把自己给掉进去了。发病机制可以用如下代码简单的勾画出来:

using namespace std;

 

int main(int argc, char* argv[])

{

    string str1 = "abcde";

    string str2 = str1;

 

    strcpy(const_cast<char*>(str2.data()), "1234");

 

    cout << "str1=" << str1 << endl

         << "str2=" << str2 << endl;

 

    return 0;

}

 

在windows+msvc 中的输出是:

str1=abcde
str2=1234

在linux+gcc中的输出是:

str1=1234
str2=1234

在boost::serialization中,对string的load也是采用这样的hack方式,目的也是为了减少内存用量并且减少一次内存拷贝。使用boost::serialization的同志们需要注意,不要掉进这个陷阱!

我们可以看出,在msvc中,string拷贝时是真拷贝,而在gcc中,必定是用了引用计数+copy on write。str1和str2内部引用的是同一块内存。因为string.data()和string.c_str()都是const成员,所以不会有copy,只会增加引用计数。所以导致修改str2实际上也修改了str1。

c++标准甚至允许把const string的成员放入带写保护的内存区域中,或者把string的成员实际上存储在不相邻的内存块中,而仅在调用 string.data() 或 string.c_str() 时将数据拷贝到一块临时内存中然后返回,这块临时内存将在下一次调用string的一个非const成员函数时释放,如果目标平台真这样实现,往 string.data()中写数据就会导致更加微妙的错误。

 

前缀压缩词典

阅读更多关于《前缀压缩词典》

包含多个固定索引,一个可变索引,

固定索引使用一个内存池和一个数组保存项目在内存中的偏移,并且使用前缀压缩,使用空间最小(每个词条4个字节的索引空间)  

可变索引不压缩,并且可以动态插入词条,占用空间较大(每个词条20个字节的索引空间)

存储 1000 万个词,占用内存 100M 左右,平均每个词10个字节(包括了字符串空间和索引空间)。

接口采用 stl 容器的风格

cpu 的 cache 是很宝贵的——从互相平行的数组看

阅读更多关于《cpu 的 cache 是很宝贵的——从互相平行的数组看》

以前一直想不通,为什么在有些系统中,要把同一个数据结构的不同字段放入 多个互相平行的数组中,而不是放入一个结构中。 继续阅读

用C++的高级模版特性实现一个不需要IDL的RPC

阅读更多关于《用C++的高级模版特性实现一个不需要IDL的RPC》

当然,首要的是为了有用,其次是挑战自己驾驭 C++ 的能力,目前已经全部完成,并且取得了非常好的效果。使用该 RPC 的简短示例代码: 继续阅读

VC++ 6.0 的函数模版解析 bug

阅读更多关于《VC++ 6.0 的函数模版解析 bug》

struct TTT1 { int x; };
struct TTT2 { int x; };

template<class T1, class T2>
void foo(T1& t1, T2& t2)
{
 printf("111111111111/n");
}

template<class T1>
void foo(T1& t1, TTT2& t2)
{
 printf("222222222222/n");
}

void foo2()
{
 TTT1 x;
 TTT2 y;
 foo(x, y);
}

上述代码在 vc6 中编译会出错:

error C2667: ‘foo’ : none of 2 overload have a best conversion
error C2668: ‘foo’ : ambiguous call to overloaded function

在 vc2005 中,gcc 中编译都通过。本来一个好好的库,因为这个原因在 VC6 下不能用了。

使用" 参数化基类" 和" 成员函数指针" 模拟实现虚函数--在实际中的应用

阅读更多关于《使用" 参数化基类" 和" 成员函数指针" 模拟实现虚函数--在实际中的应用》

// 使用" 参数化基类" " 成员函数指针" 模拟实现虚函数--在实际中的应用。

#include <stdio.h>

#include <string.h>

/*

    病毒或许可以使用这种技术有效地实现,不过这是我在写 Windows PE 文件

    加壳程序的时候总结出来的技术。当然可以被用在恶意程序上,任何事情总有两面性!

 

    ***注意***

    VC 中测试时,必须去掉“启用增量连接”,即(/INCREMENTAL:NO)

    如果在 ShellCode 或者 Virus 中使用该技术,字符串文字量(string literal

    也必须进行地址转化,即:convert_address("string literal").

 

    使用 "参数化基类 " "成员函数指针 "模拟实现虚函数。

    可能大家都以为只有疯子才会这么干,好好的虚函数干吗不用,而偏要拐弯抹角地

    搞得这么复杂,但有时候这可能是最好地选择。举个例子吧,我写 Windows PE 文件

    加壳程序的时候,遇到一个问题:

    1. 我开发了一个框架,可以只用 C++ 来写 "壳程序 ",大家很难想到吧!

    2. 但不能使用一些 C++ 功能:虚函数,异常处理,还有 switch-case 语句

    3. C++ 异常处理可以不用,但是不能使用虚函数可能太过苛刻。

 

    当我看了 "generative programming"以后,觉得 "参数化继承 "

    或许可以满足这个需求,模拟实现虚函数。

    以下是使用 "参数化继承 "实现虚函数的框架,以及要注意的事项。

 

    BaseClass 显然,实际上是 "抽象基类 ",因为如果实例化

    "InstantialClass<ConcreteClass>" 时,ConcreteClass

    必须定义响应的 "虚函数 ",否则编译就报错。如果相应的

    BaseClass InstantialClass 定义正确无误(从代码可以

    看到,这很简单,但比较麻烦,必须注意一些细节)。因此

    ConcreteClass 编译期错误不会被推迟到运行时才发现,

    而使用其它方式模拟,就很难有这个保证。

 

    其实,如果了解 C++ 的底层对象模型,这些需要注意的细节

    很容易理解!

 

    //——-

    另外有一种 C 语言模拟 C++ 虚函数的方法,可以在我以前

    (2002 )一篇文章中看到。

*/

 

struct Context

{

    int (*printf)(const char *, …);

 

    // other external fuctions…

};

 

// construct these codes…

int entry(const Context* context)

{

    int imp_entry(const Context* context);

    return imp_entry(context);

}

 

template< typename AddressType >

AddressType convert_address(AddressType address)

{

    // if used in shell code, this must return another valid address.

#define SUPPORT_RECLOCATION

#ifdef SUPPORT_RECLOCATION

    __asm {

        call LL

LL:     pop eax

        sub eax, offset LL

        add eax, address

        mov address, eax

    }

#endif

    return address;

}

 

class BaseClass

{

#define DEFINE_VIRTUAL(ret , name, param_list, call_list ) /

protected :  /

    typedef ret (BaseClass ::*T## name) param_list ;   /

    T## name p ##name;     /

public :                 /

    ret name param_list /

    { return ( this->*this ->p## name)call_list ; }

 

//////////////////////////////////////////////////////////////////////////

// expansion of :

/*  DEFINE_VIRTUAL(int, Func1,

        (int x1, int x2),

        (   x1,    x2)

        );

*/

protected :

    typedef int (BaseClass ::*TFunc1)( int x1 , int x2);

    TFunc1 pFunc1;

public :

    int Func1(int x1, int x2 )

    {

        return ( this->*this ->pFunc1)( x1, x2 );

    //  return (this->*pFunc1)(x1, x2); // can be simplified as this line.

    }

// end expansion

//////////////////////////////////////////////////////////////////////////

 

    DEFINE_VIRTUAL( int, Func2 ,

        (int x1, int x2, int x3 ),

        (   x1 ,    x2 ,    x3 )

        );

 

public :

    template< typename BaseFuncType , typename ConcreteFuncType>

    void assign(BaseFuncType & x, ConcreteFuncType y )

    {

    // if use C Style cast like "(BaseFunType)(y)", it is danger!!

    // any member function can be assigned to x!!

    //  x = convert_address((BaseFuncType)(y)); // danger!!

        x = convert_address(static_cast <BaseFuncType>(y));

    }

    BaseClass()

    {

        pFunc1 = 0;

        pFunc2 = 0;

    }

};

 

class ConcreteClass1

{

private :

    const Context* context;

    int x1, x2 , x3;

public :

    ConcreteClass1(const Context* context)

    {

        this->context = context;

    }

    int Func1(int x1, int x2)

    {

        this-> x1 = x1 ;

        this-> x2 = x2 ;

        context->printf("ConcreteClass1::Func1/n");

        context->printf("x1=%d, x2=%d/n/n", x1, x2);

        return 0;

    }

    int Func2(int x1, int x2 , int x3)

    {

        this-> x1 = x1 ;

        this-> x2 = x2 ;

        this-> x3 = x3 ;

        context->printf("ConcreteClass1::Func2/n");

        context->printf("x1=%d, x2=%d, x3=%d/n/n", x1, x2, x3);

        return 0;

    }

};

 

class ConcreteClass2

{

private :

    const Context* context;

    int x1, x2 , x3;

public :

    ConcreteClass2(const Context* context)

    {

        this->context = context;

    }

    int Func1(int x1, int x2 )

    {

        this-> x1 = x1 ;

        this-> x2 = x2 ;

        context->printf("ConcreteClass2::Func1/n");

        context->printf("x1=%d, x2=%d/n/n", x1, x2);

        return 0;

    }

    int Func2(int x1, int x2 , int x3)

    {

        this-> x1 = x1 ;

        this-> x2 = x2 ;

        this-> x3 = x3 ;

        context->printf("ConcreteClass2::Func2/n");

        context->printf("x1=%d, x2=%d, x3=%d/n/n", x1, x2, x3);

        return 0;

    }

};

 

template <class ConcreteClass>

class InstantialClass :

    // "BaseClass" must be first base class, otherwise,

    // function pointer convert in "BaseClass::assign()" may be not valid!

    public BaseClass, // interface inherit, multi inherit is not allowed!!

    protected ConcreteClass // implementation inherit, multi inherit is allowed

{

    // it is a guide line that do not hold any data member in this class!!

    //

    // if ‘BaseClass’ is not the first base class for ‘this’ class,

    // and data member is defined here,

    // and these data member will be modified,

    // it will error at runtime!

    // you can reverse the inherit order of

    // BaseClass and ConcreteClass, and try!!

    //

    int x1, x2 , x3;

public :

    // must delegate these member functions…

    int Func1(int x1, int x2 ) { return ConcreteClass::Func1 (x1, x2); }

    int Func2(int x1, int x2 , int x3)

    {

        this-> x1 = x1 ;

        this-> x2 = x2 ;

        this-> x3 = x3 ;

        return ConcreteClass::Func2 (x1, x2, x3 );

    }

 

    InstantialClass(const Context* context)

        : ConcreteClass(context)

    {

    // must assign these member function pointers…

    //  BaseClass::pFunc1 = (TFunc1)(Func1);

    //  int v = _MSC_VER;

#if _MSC_VER >= 1310 // vc2003

        assign(pFunc1, Func1);

        assign(pFunc2, Func2);

#else // in vc6, vc6 template support is not perfect!!

        pFunc1 = (TFunc1)Func1;

        pFunc2 = (TFunc2)Func2;

#endif

    // if use C Style cast in assign, follow line can be compiled,

    // but will error at runtime, because pointer to ConcreteClass

    // is different from to ‘this’!!

    // pointer to ‘BaseClass’ is equal to ‘this’..

    // so, do not write such code,

    // must delegate ‘ConcreteClass::Func2’ to ‘this->Func2’.

    // assign(pFunc2, ConcreteClass::Func2);

    }

};

 

template InstantialClass<ConcreteClass1>;

template InstantialClass<ConcreteClass2>;

 

int imp_entry(const Context* context)

{

    InstantialClass<ConcreteClass1> v1(context);

    InstantialClass<ConcreteClass2> v2(context);

    BaseClass* p1 = &v1;

    BaseClass* p2 = &v2;

 

    p1-> Func1(1111 , 2222);

    p2-> Func1(1111 , 2222);

    p1-> Func2(1111 , 2222, 3333);

    p2-> Func2(1111 , 2222, 3333);

 

    p1-> Func1(1111 , 2222);

    p2-> Func1(1111 , 2222);

    p1-> Func2(1111 , 2222, 3333);

    p2-> Func2(1111 , 2222, 3333);

 

    return 0;

}

 

// template instantiation generated functions were not

// lie in ‘entry()’ and ‘end_address()’.

// this is a problem, we must guess the actual codes size.

//

// it looks as if this is not a good solution for small shell codes.

//

// but in my shell code, I put all code in a PE file,

// and relocate the whole code section of the PE file.

// so, this is good idea for shell code.

// this technic is a good solution for virus,

// —- if you want write virus in C++ and use virtual function!

void end_address() {}

 

int main(int argc , char* argv[])

{

    Context context;

    context.printf = printf;

 

    typedef int (*T_entry)(const Context* context);

    unsigned char codeBuffer[16*1024];

    size_t codeSize = (unsigned char*)end_address – (unsigned char*)entry;

    size_t guessedCodeSize = sizeof(codeBuffer);

//  memcpy(codeBuffer, (void*)entry, codeSize); // run error!!

    memcpy(codeBuffer, (void*)entry, guessedCodeSize); // run ok!!

 

//  entry(&context);

    ((T_entry)(void*)codeBuffer)(&context);

 

    return 0;

}

 

 

 

使用" 参数化基类" 和" 成员函数指针" 模拟实现虚函数

阅读更多关于《使用" 参数化基类" 和" 成员函数指针" 模拟实现虚函数》

// 使用" 参数化基类"" 成员函数指针" 模拟实现虚函数。

#include "stdafx.h"

 

/*

    使用 "参数化基类 " "成员函数指针 "模拟实现虚函数。

    可能大家都以为只有疯子才会这么干,好好的虚函数干吗不用,而偏要拐弯抹角地

    搞得这么复杂,但有时候这可能是最好地选择。举个例子吧,我写 Windows PE 文件

    加壳程序的时候,遇到一个问题:

    1. 我开发了一个框架,可以只用 C++ 来写 "壳程序 ",大家很难想到吧!

    2. 但不能使用一些 C++ 功能:虚函数,异常处理,还有 switch-case 语句

    3. C++ 异常处理可以不用,但是不能使用虚函数可能太过苛刻。

 

    当我看了 "generative programming"以后,觉得 "参数化继承 "

    或许可以满足这个需求,模拟实现虚函数。

    以下是使用 "参数化继承 "实现虚函数的框架,以及要注意的事项。

 

    BaseClass 显然,实际上是 "抽象基类 ",因为如果实例化

    "InstantialClass<ConcreteClass>" 时,ConcreteClass

    必须定义响应的 "虚函数 ",否则编译就报错。如果相应的

    BaseClass InstantialClass 定义正确无误(从代码可以

    看到,这很简单,但比较麻烦,必须注意一些细节)。因此

    ConcreteClass 编译期错误不会被推迟到运行时才发现,

    而使用其它方式模拟,就很难有这个保证。

 

    其实,如果了解 C++ 的底层对象模型,这些需要注意的细节

    很容易理解!

 

    //——-

    另外有一种 C 语言模拟 C++ 虚函数的方法,可以在我以前

    (2002 )一篇文章中看到。

*/

class BaseClass

{

#define DEFINE_VIRTUAL(ret , name, param_list, call_list ) /

protected :  /

    typedef ret (BaseClass ::*T## name) param_list ;   /

    T## name p ##name;     /

public :                 /

    ret name param_list /

    { return ( this->*this ->p## name)call_list ; }

 

//////////////////////////////////////////////////////////////////////////

// expansion of :

/*  DEFINE_VIRTUAL(int, Func1,

        (int x1, int x2),

        (   x1,    x2)

        );

*/

protected :

    typedef int (BaseClass ::*TFunc1)( int x1 , int x2);

    TFunc1 pFunc1;

public :

    int Func1(int x1, int x2 )

    {

        return ( this->*this ->pFunc1)( x1, x2 );

    //  return (this->*pFunc1)(x1, x2); // can be simplified as this line.

    }

// end expansion

//////////////////////////////////////////////////////////////////////////

 

    DEFINE_VIRTUAL( int, Func2 ,

        (int x1, int x2, int x3 ),

        (   x1 ,    x2 ,    x3 )

        );

 

protected :

    template< typename AddressType >

    AddressType convert_address(AddressType address)

    {

        // if used in shell code, this must return another valid address.

        return address;

    }

public :

    template< typename BaseFuncType , typename ConcreteFuncType>

    void assign(BaseFuncType & x, ConcreteFuncType y )

    {

    // if use C Style cast like "(BaseFunType)(y)", it is danger!!

    // any member function can be assigned to x!!

    //  x = convert_address((BaseFuncType)(y)); // danger!!

        x = convert_address(static_cast <BaseFuncType>( y));

    }

    BaseClass()

    {

        pFunc1 = 0;

        pFunc2 = 0;

    }

};

 

class ConcreteClass1

{

private :

    int x1, x2 , x3;

public :

    int Func1(int x1, int x2 )

    {

        this-> x1 = x1 ;

        this-> x2 = x2 ;

        std:: cout << "ConcreteClass1::Func1" << "/n" ;

        std:: cout << "x1=" << x1 << ", x2=" << x2 << "/n/n";

        return 0;

    }

    int Func2(int x1, int x2 , int x3)

    {

        this-> x1 = x1 ;

        this-> x2 = x2 ;

        this-> x3 = x3 ;

        std:: cout << "ConcreteClass1::Func2" << "/n" ;

        std:: cout << "x1=" << x1 << ", x2=" << x2 << ", x3=" << x3 << "/n/n";

        return 0;

    }

};

 

class ConcreteClass2

{

private :

    int x1, x2 , x3;

public :

    int Func1(int x1, int x2 )

    {

        this-> x1 = x1 ;

        this-> x2 = x2 ;

        std:: cout << "ConcreteClass2::Func1" << "/n" ;

        std:: cout << "x1=" << x1 << ", x2=" << x2 << "/n/n";

        return 0;

    }

    int Func2(int x1, int x2 , int x3)

    {

        this-> x1 = x1 ;

        this-> x2 = x2 ;

        this-> x3 = x3 ;

        std:: cout << "ConcreteClass2::Func2" << "/n" ;

        std:: cout << "x1=" << x1 << ", x2=" << x2 << ", x3=" << x3 << "/n/n";

        return 0;

    }

};

 

template <class ConcreteClass>

class InstantialClass :

    // "BaseClass" must be first base class, otherwise,

    // function pointer convert in "BaseClass::assign()" may be not valid!

    public BaseClass, // interface inherit, multi inherit is not allowed!!

    protected ConcreteClass // implementation inherit, multi inherit is allowed

{

    // it is a guide line that do not hold any data member in this class!!

    //

    // if ‘BaseClass’ is not the first base class for ‘this’ class,

    // and data member is defined here,

    // and these data member will be modified,

    // it will error at runtime!

    // you can reverse the inherit order of

    // BaseClass and ConcreteClass, and try!!

    //

    int x1, x2 , x3;

public :

    // must delegate these member functions…

    int Func1(int x1, int x2 ) { return ConcreteClass::Func1 (x1, x2); }

    int Func2(int x1, int x2 , int x3)

    {

        this-> x1 = x1 ;

        this-> x2 = x2 ;

        this-> x3 = x3 ;

        return ConcreteClass::Func2 (x1, x2, x3 );

    }

 

    InstantialClass()

    {

    // must assign these member function pointers…

    //  BaseClass::pFunc1 = (TFunc1)(Func1);

        assign( pFunc1, Func1 );

        assign( pFunc2, Func2 );

 

    // if use C Style cast in assign, follow line can be compiled,

    // but will error at runtime, because pointer to ConcreteClass

    // is different from to ‘this’!!

    // pointer to ‘BaseClass’ is equal to ‘this’..

    // so, do not write such code,

    // must delegate ‘ConcreteClass::Func2’ to ‘this->Func2’.

    // assign(pFunc2, ConcreteClass::Func2);

    }

};

 

int _tmain( int argc , _TCHAR* argv[])

{

    BaseClass* p1 = new InstantialClass< ConcreteClass1>();

    BaseClass* p2 = new InstantialClass< ConcreteClass2>();

 

    p1-> Func1(1111 , 2222);

    p2-> Func1(1111 , 2222);

    p1-> Func2(1111 , 2222, 3333);

    p2-> Func2(1111 , 2222, 3333);

 

    p1-> Func1(1111 , 2222);

    p2-> Func1(1111 , 2222);

    p1-> Func2(1111 , 2222, 3333);

    p2-> Func2(1111 , 2222, 3333);

 

    delete p2;

    delete p1;

 

    return 0;

}

 

 

stl::set 的一个缺陷

阅读更多关于《stl::set 的一个缺陷》

stl::set  什么都好,就一点不好:不能仅仅通过 key去查找元素。

例如