C++11 では関数の中で関数オブジェクトの宣言ができるようになっています。
スクリプト系言語などでもよく見る書き方で、下記は関数を値として代入しています。

void func_test()
{
  auto func_obj= []( int a ){
    return a + 100;
  };

  func_obj( 23 );
}

関数ポインタとの違いは、関数や式の中に実体をインラインで無名宣言できることと、
実行コードだけでなく、オブジェクトの中にデータ領域も併せ持つことです。
要するに関数オブジェクトで、以前だと下記のような書き方をしていました。

void func()
{
  struct LocalFuncT {
     int operator()( int a ){
       return a + 100;
     }
  };
  LocalFuncT func_obj= LocalFuncT();

  func_obj( 23 );
}

ただし関数内で宣言された class ↑は template に渡すことができないなど
制限があります。(VisualC++ ではできました)

C++11 ではこの制限が撤廃されると同時に、Lambda 関数としてより簡単に
宣言して使えるようになっています。

template<typename FT>
struct CallbackObject {
  FT Func;
  CallbackObject( FT func ) : Func( func )
  {
  }
  int operator()( int a )
  {
    return  Func( a );
  }
};
template<typename T>
CallbackObject<T> CreateCallback( FT func )
{
  return  CallbackObject<FT>( func );
}

int s_func( int a )
{
  return a + 200;
}

struct FuncObject {
  int operator()( int a )
  {
     returtn a + 300;
  }
};

void func_test()
{
  CreateCallback( s_func )( 12 );  // pointer
  CreateCallback( FuncObject() )( 12 ); // object
  CreateCallback( []( int a ){ return a + 100; } )( 12 ); // lambda
}

今まで関数オブジェクトを扱っていた部分を Lambda 関数で置き換えられます。
オブジェクトなので引数の型を取り出そうとしてもそのままでは関数の型には
マッチしませんが、operator() は取れるようです。

template<typename TT, typename FT>
struct CallbackObject {
  TT* This;
  FT  Func;
  CallbackObject( TT* t, FT func ) : This( t ), Func( func ) {}
  int operator()( int a )
  {
    return  (This->*Func)( a );
  }
};
template<typename TT, typename FT>
CallbackObject<TT,FT> CreateCallback( TT* t, FT func )
{
  return  CallbackObject<TT,FT>( t, func );
}

struct func_class {
  void func_test()
  {
    auto func_obj2= [=]( int a ){ dump(this); return a + 400; };

    func_obj2( 12 );

    func_obj2.operator()( 12 );

    typedef decltype( func_obj2 )  FUNC_T2;
    CreateCallback( &func_obj2, &FUNC_T2::operator() )( 12 );
  }
};

特徴的なのはクロジャーとして変数を自動的に取り込めることで、
関数オブジェクトのメンバとして格納されています。
実際に vc/gcc でキャプチャなしの場合 struct {} 同様に sizeof() が 1 を返しており、
キャプチャ変数が存在する場合はキャプチャ分のサイズになります。

this はキャプチャ変数とみなされるため、自分自身へアクセスする方法は無いようです。
つまりキャプチャ変数に対する暗黙の "this->" が存在しており、
this も this->this 相当となっています。
キャプチャはコピー ([=]) または参照 ([&]) となり、
move ([&&]?) にはならないようです。


関連エントリ
C++11 Variadic Templates 可変長引数のテンプレート
スレッド同期命令の比較 C++11 とコンパイラ
C++11 Rvalue Reference 右辺値参照


C++11 では Template で不定個の引数をまとめて扱えるようになっています。

void func00( int a0, float a1, int a2 )
{
}

template<typename... A0>
void Exec00( A0... a0 )
{
   func00( a0... );
}

void VT_Test()
{
   Exec00<int,float,int>( 1, 2.0f, 3 );
}

例えば上の例は VT_Test() からそのまま Exec00 経由で func00() を
呼び出しています。
func00() の引数の数が変わっても Tempalte の宣言を変える必要がなくなります。

今までこのようなケースでは、Template にマッチさせるために
引数の数だけオーバーロードか特殊化が必要でした。

template<typename A0>
void Exec01( A0 a0 )
{
   func00( a0 );
}
template<typename A0, typename A1>
void Exec01( A0 a0, A1 a1 )
{
   func00( a0, a1 );
}
template<typename A0, typename A1, typename A2>
void Exec01( A0 a0, A1 a1, A2 a2 )
{
   func00( a0, a1, a2 );
}
~

他にも参照で問題が生じます。
↑ Exec01 はどんな引数でもコンパイルが通りますが、↓ func01() のような
参照を受け取る関数に対して意図しないコピーが渡ってしまいます。

void func01( int& a0 )
{
}

↓ Template 側を参照にすれば解決しますが、今度は定数値(右辺値)を
渡すことができず、Exec02( 100 ) などの呼び出しがエラーになります。

template<typename A0>
void Exec02( A0& a0 )
{
~

定数など右辺値に対しては const の参照も必要となるので、結局
引数の数に加えて参照の組みわせの分だけ宣言が必要となります。

template<typename A0>
void Exec03( A0& a0 )
{
   func00( a0 );
}
template<typename A0>
void Exec03( const A0& a0 )
{
   func00( a0 );
}
template<typename A0, typename A1>
void Exec03( A0& a0, A1& a1 )
{
   func00( a0, a1 );
}
template<typename A0, typename A1>
void Exec03( const A0& a0, A1& a1 )
{
   func00( a0, a1 );
}
template<typename A0, typename A1>
void Exec03( A0& a0, const A1& a1 )
{
   func00( a0, a1 );
}
~

C++ の Rvalue Reference は Move operation だけでなく、
↓参照の転送の問題も解決してくれます。

template<typename A0>
void Exec04( A0&& a0 )
{
   func00( std::forward<A0>( a0 ) );
}
~

const 無しに右辺値も左辺値も参照を渡すことができるようになりました。
さらに forward をつけると右辺値参照もそのまま転送されます。

可変引数と合わせると非常に簡単で、これひとつで多くの組み合わせに
対応できることになります。

template<typename... A0>
void Exec05( A0&&... a0 )
{
   func00( std::forward<A0>( a0 )... );
}

様々なプラットフォームをターゲットにしていると、
コンパイラの対応状況が問題になってきます。

iOS/OSX の Xcode や Android NDK は頻繁に更新されており、
比較的新しい clang/gcc が使えるので問題はないようです。

特に Android NDK の r8e に至っては、 toolchain として
gcc 4.4.3/4.6/4.7, clang 3.1/3.2 の 5種類ものコンパイラが含まれており
選択可能となっています。

VisualStudio 2012 は未対応ですが、November 2012 CTP として
Variadic Templates 対応のコンパイラが公開されています。
正式対応もそう遠くないと考えられます。

NaCl pepper_26 の x86 は gcc 4.4.3 なのでまだコンパイルが通りません。
arm は 4.7.3 が使われているようです。


関連エントリ
スレッド同期命令の比較 C++11 とコンパイラ
C++11 Rvalue Reference 右辺値参照


スレッド間の同期を取る方法は複数あります。
OS のカーネルオブジェクト、ライブラリ関数、OS 依存の API など。
他にも CAS のような最低限の atomic 操作を使って Spinlock を作ることができます。
もちろんハードウエアスレッド環境に限ります。

C++11 対応に伴いコンパイラも新しい atomic 命令に移行しています。
例えば gcc の builtin 命令は __sync_ から __atomic_ となり、
clang にも同様に __c11_atomic_ があります。

Windows の場合は OS に Interlocked~ API がありますが、
VC++ が生成する同名の Intrinsic バージョンもあります。
こちらも C++11 に従いメモリバリアを指定できる命令が追加されているようです。

// CAS
gcc      __atomic_compare_exchange_*()
clang    __c11_atomic_compare_exchange_*()
Windows  _InterlockedCompareExchange_*()

gcc 4.7.2 : Built-in functions for memory model aware atomic operations
Clang 3.3 : Clang Language Extensions
msdn : x86 Intrinsics List


↓各種 API による比較です。

(1) Windows             Win8 x86   Win8 x64
-------------------------------------------------------
SpinLock(intrinsic)         5852       6353
CriticalSection            20894      22610
Win32 Mutex              2537901    3790403


(2) Linux              Linux x86   Linux x64   Linux ARM
-------------------------------------------------------
SpinLock(gcc atomic)      12712       10641       27977
pthread mutex             13693       11796       23807


(3) MacOS X         MacOS X x86  MacOS X x64
-------------------------------------------------------
SpinLock(OSAtomic)         9505        9070
pthread mutex            563013      467785

// 実行時間 単位=us

3つのスレッドで同じカウンタをそれぞれ 50000回カウントしています。
100% 競合する状態で、ロック手段による違いを測定しています。

走らせた PC のスペックが異なることと、数値の変動が大きいため
単純な比較ができないのでご注意ください。
同じ OS 内で API による違いだけ見てください。


(1) Windows (Windows 8 + VS2012 Express)

カーネルオブジェクトである Mutex は非常に遅くなっています。
CriticalSection の方が2桁高速ですが、_Interlocked 命令を使った
SpinLock の方が更に速い結果となっています。


(2) Linux (Ubuntu 12.10 / 13.04)

x86/x64 は VMware Player によるテストなのでご了承ください。
特筆すべき点は pthread_mutex が SpinLock と全く変わらないか、
むしろ速いということです。

SpinLock を作る場合に atomic API の使い方を間違えると、
pthread_mutex よりも遅くなったり、ロックに失敗したりするようです。

X86/x64 の場合 gcc (4.7.2) builtin の __atomic_ でメモリアクセス方法に
__ATOMIC_SEQ_CST を指定すると pthread_mutex よりも低速でした。
__ATOMIC_RELAXED で pthread と同等になります。
また Legacy API ですが __sync_ を使った場合も pthread と同等の
速度となりました。

ARM (Cortex-A9) では RELAXED の場合メモリアクセスが競合し
正しくブロックされません。
上記テストは ARM の場合のみ SEQ_CST を指定しています。
また __sync_ も RELAXED 相当なためかロックされませんでした。


(3) MacOS X (10.8)

OSAtomic と phtread_mutex で大きな隔たりがあります。
同じ pthread の mutex でも、Linux と違い Windows のように
カーネルオブジェクトとして実装されている可能性があります。


API が同一でも OS と CPU によって挙動が違うことがわかりました。
Linux では pthread_mutex が最もよい選択ですが、
MacOS X では逆に pthread_mutex を避けた方が良いようです。


テストプログラムは下記のとおり。

template<typename LockT>
class LockTest {
    LockT  lock;
    static int Counter;
    enum {
        MAX_LOOP= 50000
    };
public:
    void thread_func()
    {
        for( int i= 0 ; i< MAX_LOOP ; i++ ){
            lock.Lock();
            Counter++;
            lock.Unlock();
        }
    }
    void Run()
    {
        ClockTimer time;

        Counter= 0;
        Thread t0, t1, t2;

        t0.Run( this, &LockTest<LockT>::thread_func );
        t1.Run( this, &LockTest<LockT>::thread_func );
        t2.Run( this, &LockTest<LockT>::thread_func );

        t0.Join();
        t1.Join();
        t2.Join();

        time.Output();
        assert( Counter == MAX_LOOP * 3 );
    }
};


関連?エントリ
C++11 Rvalue Reference 右辺値参照


C++11 の新機能を使うとオブジェクトの無駄なコピーを減らすことができます。

記述が簡単になるなど、C++11 にはいろいろ便利に使える追加機能があります。
Rvalue Reference の場合はむしろコードが増えるのですが、
うまく使うことでプログラムの動作を効率化することができます。
とりあえず適当に文字列型を作ってみます。

class MyString {
    char*   Name;
private:
    void Clear()
    {
        delete[] Name;
        Name= NULL;
    }
    void Copy( const char* str )
    {
        Clear();
        size_t  length= strlen(str)+1;
        Name= new char[length];
        memcpy( Name, str, sizeof(char)*length );
    }
    void DeepCopy( const MyString& src )
    {
        Copy( src.Name );
    }
public:
    MyString() : Name( NULL ) {}
    MyString( const MyString& src ) : Name( NULL )
    {
        DeepCopy( src );
    }
    MyString( const char* str ) : Name( NULL )
    {
        Copy( str );
    }
    ~MyString()
    {
        Clear();
    }
    MyString& operator=( const MyString& src )
    {
        DeepCopy( src );
        return  *this;
    }
};

copy 時に文字列バッファの複製が発生します。
例えばこんな感じ。

MyString  str1;
MyString  str2;
str1= "abcdefg";              // -- (1)
str2= MyString( "ABCDEF" );   // -- (2)
str1= str2;                   // -- (3)

C++11 の Rvalue Reference を使うと特定のケースで copy を move に変換出来ます。
対応コードを追加したのが下記の形。

#include    <string.h>
#include    <utility>

#ifndef IS_CPP11
# define    IS_CPP11    (__cplusplus > 199711L)
#endif

int  AllocCount;
int  FreeCount;

class MyString {
    char*   Name;
private:
    void Clear()
    {
        if( Name ){
            FreeCount++;
        }
        delete[] Name;
        Name= NULL;
    }
    void Copy( const char* str )
    {
        Clear();
        size_t  length= strlen(str)+1;
        Name= new char[length];
        memcpy( Name, str, sizeof(char)*length );
        AllocCount++;
    }
    void DeepCopy( const MyString& src )
    {
        Copy( src.Name );
    }
public:
    MyString() : Name( NULL ) {}
    MyString( const MyString& src ) : Name( NULL )
    {
        DeepCopy( src );
    }
    MyString( const char* str ) : Name( NULL )
    {
        Copy( str );
    }
    ~MyString()
    {
        Clear();
    }
    MyString& operator=( const MyString& src )
    {
        DeepCopy( src );
        return  *this;
    }
    //-- ↓ここから追加分
#if IS_CPP11
    MyString( MyString&& src )
    {
        Name= src.Name;
        src.Name= NULL;
    }
    MyString& operator=( MyString&& src )
    {
        char*   tmp= Name;
        Name= src.Name;
        src.Name= tmp;
        return  *this;
    }
#endif
};

これで転送元を捨てても構わない場合に copy ではなく破壊転送 (move) が行われます。
上の場合はメモリを確保し直す必要がないので効率が上がります。

転送元を捨てても構わないケースの代表が、
一時確保されたオブジェクトである rvalue 右辺値です。
また明示的に std::move() をつければ任意の値を破壊転送することができます。

これで C++11 の場合は (1),(2) において copy が消えます。
どちらも一時的な object が作られているからです。

さらに適当なベクターを作ってみます。

template<typename T>
class MyVector {
    T*      Buffer;
    T*      Ptr;
    size_t  BufferSize;
private:
    void Clear()
    {
        delete[] Buffer;
        Buffer= Ptr= NULL;
    }
public:
    MyVector() : Buffer( NULL ), Ptr( NULL ), BufferSize( 0 ) {};
    MyVector( size_t size ) : BufferSize( size )
    {
        Ptr= Buffer= new T[size];
    }
    ~MyVector()
    {
        Clear();
    }
    size_t Size() const
    {
        return  Ptr - Buffer;
    }
    T& operator[]( size_t index )
    {
        return  Buffer[index];
    }
    const T& operator[]( size_t index ) const
    {
        return  Buffer[index];
    }
    void PushBack( const T& src )
    {
        assert( Ptr < Buffer + BufferSize );
        *Ptr++= src;
    }
#if IS_CPP11
    void PushBack( T&& src )
    {
        assert( Ptr < Buffer + BufferSize );
        *Ptr++= std::forward<T>(src);
    }
#endif
};

ループさせてどの程度高速化されるか測ってみます。

// main.cpp
int main()
{
    AllocCount= 0;
    FreeCount= 0;
    {
        const int   LOOP_MAX= 100000;
        MyVector<MyString>  string_list( LOOP_MAX );

        for( int i= 0 ; i< LOOP_MAX ; i++ ){
            string_list.PushBack( "12345" );
        }
    }
    printf( "alloc=%d  free=%d\n", AllocCount, FreeCount );
    return  0;
}

差が出るように作ったので C++11 でコンパイルした方が速いのは当然なのですが、
実際にメモリ確保の回数が半減していることがわかります。

Prog  time         output
c03   0m0.015s     alloc=200000  free=200000
c11   0m0.009s     alloc=100000  free=100000


# Makefile
mac:
	clang++ -std=c++11 -stdlib=libc++  -O4 main.cpp  -o c11
	clang++ -std=c++03 -stdlib=libc++  -O4 main.cpp  -o c03

linux:
	g++ -std=c++11   -O4 main.cpp  -o g11
	g++ -std=c++03   -O4 main.cpp  -o g03
	clang++ -std=c++11 -O3 main.cpp  -o c11
	clang++ -std=c++03 -O3 main.cpp  -o c03

win:
	cl /O2 main.cpp /Fev11.exe -DIS_CPP11=1
	cl /O2 main.cpp /Fev03.exe -DIS_CPP11=0

特定のケースで明らかな無駄を省いているだけなので、通常はここまで差がでません。
例えば (3) の場合何もせずに copy を減らすことはできません。

move の場合は結局 move 用に別の API を設けていることになります。
またオブジェクトだけでなく、copy が発生する API を経由する場合も
copy の他に move 用の別のルートを追加する必要があります。
途中で move() や forward() を付け忘れると、オブジェクトまで到達しないで
途切れてしまいます。

上の MyVector 自体も複製できるようにしてみます。

template<typename T>
class MyVector {
    T*      Buffer;
    T*      Ptr;
    size_t  BufferSize;
private:
    void Clear()
    {
        delete[] Buffer;
        Buffer= Ptr= NULL;
    }
#if IS_CPP11
    void Move( MyVector&& src )
    {
        Clear();
        BufferSize= src.BufferSize;
        Ptr= Buffer= new T[BufferSize];
        for( int i= 0 ; i< BufferSize ; i++ ){
            *Ptr++= std::move(src[i]);  //-- (4)
        }
    }
#endif
    void DeepCopy( const MyVector& src )
    {
        Clear();
        BufferSize= src.BufferSize;
        Ptr= Buffer= new T[BufferSize];
        for( int i= 0 ; i< BufferSize ; i++ ){
            *Ptr++= src[i];
        }
    }
public:
    MyVector() : Buffer( NULL ), Ptr( NULL ), BufferSize( 0 ) {};
    MyVector( size_t size ) : BufferSize( size )
    {
        Ptr= Buffer= new T[size];
    }
    ~MyVector()
    {
        Clear();
    }
    size_t Size() const
    {
        return  Ptr - Buffer;
    }
    void PushBack( const T& src )
    {
        assert( Ptr < Buffer + BufferSize );
        *Ptr++= src;
    }
#if IS_CPP11
    void PushBack( T&& src )
    {
        assert( Ptr < Buffer + BufferSize );
        *Ptr++= std::forward<T>(src);
    }
#endif
    T& operator[]( size_t index )
    {
        return  Buffer[index];
    }
    const T& operator[]( size_t index ) const
    {
        return  Buffer[index];
    }


    MyVector& operator=( const MyVector& src )
    {
        DeepCopy( src );
        return  *this;
    }
#if IS_CPP11
    MyVector& operator=( MyVector&& src )
    {
        Move( std::move(src) );
        return  *this;
    }
#endif
};

下記のように MyVector を代入できるようになりました。
この (5) のケースでは完全な move となり string のメモリ確保が発生しません。

int main()
{
    AllocCount= 0;
    FreeCount= 0;
    {
        const int   LOOP_MAX= 100000;
        MyVector<MyString>  string_list( LOOP_MAX );

        for( int i= 0 ; i< LOOP_MAX ; i++ ){
            string_list.PushBack( "12345" );
        }

        MyVector<MyString>  string_list2( LOOP_MAX );
        string_list2= std::move(string_list);    // -- (5)
    }
    printf( "alloc=%d  free=%d\n", AllocCount, FreeCount );
    return  0;
}

ですが std::move() を付け忘れると move でなく MyString の copy になります。
下記のように alloc/free が増えます。(5) でも同じです。

c03    alloc=300000  free=300000
c11    alloc=200000  free=200000   ( (5) or (6) の move 無し )
c11    alloc=100000  free=100000



2010/08/24
fastmake

以前は VisualStudio の nmake を使っていました。
最近は fastmake を使っています。

fastmake


●特徴

・高速動作させるための様々な記述が可能。
・豊富な組み込みコマンド。
・Windows native で exe ファイル一つのみ。
・基本的に gmake 互換。
・-j の並列化可能。ただし .PARALLEL 宣言したルールのみ
・コマンド行は TAB じゃなくても良い。


●高速な理由

一番効果的だと感じたのがサブシェル呼出を徹底的に廃したこと。
例えば普通の make だとコマンド実行でシェルを経由するので余計に時間がかかります。
echo や del などのよく使う命令もシェルコマンドなのでシェルが走っています。

# nmake
echo_abc:
    @echo A
    @echo B
    @echo C

この場合 shell が 3回起動するので非常に低速です。
(UNIX shell だと (~;~) 等で呼び出しをまとめたりできる。)

fastmake は echo, rm, mkdir, cd 等よく使う機能が組み込まれており、
サブシェル呼出を必要としません。
コンパイラなどのコマンド実行もシェルを経由せずに直接 exec 可能です。

# fastmake
echo_abc:
    $(echo A)
    $(echo B)
    $(echo C)

%.obj .NOSHELL .PARALLEL : %.cpp
    $(CC) $(CFLAGS) -c -o $@ $<

$(echo~) は組み込み関数です。リダイレクトの代わりに $(echo ~,outpufile)
で直接出力ファイル名を指定することも可能です。
.NOSHELL 指定はシェルを経由せずに直接コマンドを exec しています。

Makefile 中さらに別の Makefile を呼び出すことがよくあります。
やはり通常は make の呼出のためにシェルを経由します。

# nmake
android:
    $(MAKE) -f Android.mak all

fastmake は自分自身を呼び出す組み込み関数があります。

# fastmake
android .PHONY :
    $(make -f Android.mak all)

この場合同一プロセス内で実行可能で、シェル呼び出しも外部コマンドの呼出も
スキップします。プロセス起動が無いため高速です。

フォルダまるごと削除も簡単に。

# nmake で rm -fr $(OBJDIR) 相当
OBJDIR = obj
clean:
    if exist $(OBJDIR) rmdir /Q /S $(OBJDIR)



# fastmake
OBJDIR = obj
clean:
    $(rm $(OBJDIR))


●便利な機能など

ドキュメントにも書いてありますが .COMBINE 指定があります。

%.obj: %.cpp
    cl $@ $<

等のパターンマッチは、生成するファイルごとに個別にコマンドを実行します。

cl a.cpp
cl b.cpp
cl c.cpp
~

.COMBINE 指定すると 1回のコマンド呼び出しにまとめて実行されます。

%.obj .COMBINE : %.cpp
    cl $@ $<


cl a.cpp b.cpp c.cpp ~

フォルダごとに make -f Win32.mak clean を実行する場合次のように書けます。

# fastmake
WIN32_DIRS = math file d3d11 gl3 gles2 network script
clean_win32 .PHONY :
    $(foreach dir,$(WIN32_DIRS),$(make -C $(dir) -f Win32.mak clean))

ドキュメントには下記の方法も紹介されていますが単純に foreach した方が速いようです。

# fastmake
WIN32_DIRS = math file d3d11 gl3 gles2 network script
clean_win32 .PHONY :  $(WIN32_DIRS:+.win32clean) ;
%.win32clean .PHONY :
    $(make -C $* -f Win32.mak clean)

内部コマンドは副作用として実行される関数扱いなので、マクロ置換できる場所なら
どこにでも書けます。下記の make もマクロ展開時に実行されます。

DIRS = a b c
A:=$(foreach dir,$(DIRS),$(make -f sub.mak))

結果が不要な内部コマンドは空文字になるようです。

$(echo $(echo B)A$(echo C))
 ↓
B
C
A


●気がついたことなど

環境変数はデフォルトでは取り込まれません。
個別に .IMPORT 指定が必要です。

.IMPORT .IGNORE : PATH DXSDK_DIR

変数 include への代入が出来ません。

include = $(DXSDK_DIR)\\include

コンパイラへ渡す環境変数として "include" が用いられることがありますが、
上のように書くと include 命令と勘違いしてしまうようです。
export 宣言することで回避できます。

export include=$(DXSDK_DIR)\\include

Makefile 中に C言語のブロックコメント /*~*/ が使えるように拡張されています。
最近これが問題になることが分かってきました。

COPYFILE_SRC = system/*.dds

こんな感じの記述があると、system"/*".dds の "/*" 以降がコメント扱いになり
無視されてしまいます。エラーが出ないので厄介です。

.FOREACH が意外に汎用性がありません。
パターン定義以外ではあまりうまく機能しないようです。

$(make ~) の make 呼び出しでは -j 指定を変更できません。
下の例の (1) では job 数が変わりません。
job 数を変更する場合は (2) のように自分自身を実行しなおす必要があります。

# (1)
android .PHONY :
    $(make -j 10 -f Android.mak all)

# (2)
FMAKE = fastmake.exe
android .PHONY .NOSHELL :
    $(FMAKE) -j 10 -f Android.mak all

nmake だとファイルの存在確認が簡単にできますが fastmake ではすっきり
書けません。要研究。

# nmake
MAYAPATH = C:\Program Files\Autodesk\Maya2011
!if EXIST( "$(MAYAPATH)" )
~
!endif


# fastmake
PROGFILES = C:\\Program Files
PROGFILES_AUTO = $(PROGFILES)\\Autodesk
.IF $(ls $(PROGFILES),d,Autodesk) != ""
  .IF $(ls $(PROGFILES_AUTO),d,Maya2011) != ""
~
  .END
.END



<<前のページ(日付が新しい方向) | 次のページ(日付が古い方向)>>