3

これら2つの関数プロトタイプの違いは何ですか?

void apply1(double(f)(double));
void apply2(double(*f)(double));

提供された関数を配列に適用することが目標である場合、他のバージョンと比較して高速なバージョンはありますか?

編集:実装の例:

#include <iostream>
#include <vector>
#include <cmath>

// First version
template<typename Type> void apply1(std::vector<Type>& v, Type(f)(Type))
{
    for (unsigned int i = 0; i < v.size(); ++i) {
        v[i] = f(v[i]);
    }
}

// Second version
template<typename Type> void apply2(std::vector<Type>& v, Type(*f)(Type))
{
    for (unsigned int i = 0; i < v.size(); ++i) {
        v[i] = f(v[i]);
    }
}

// Main
int main()
{
   std::vector<double> v = {1., 2., 3., 4., 5.};
   apply1(v, std::sin);
   apply2(v, std::sin);
   return 0;
}
4

3 に答える 3

5

まず、テンプレートラッパーのインスタンス化の速度は、ほぼ完全にオプティマイザーに翻弄されます。

そうは言っても、特に関数パラメーターの呼び出しをチェックするために、サンプルを私が考えることができる最も基本的なコードに減らしました。読み進めることはできますが、まったく同じように呼び出されることがわかります。ある宣言と別の宣言には利点がありません。さらに、あなたが省略したものを含めました(reference-decl)

#include <cstdio>

int hello(int x)
{
    return x;
}

template<typename Type> 
void apply1(Type x, Type (f)(Type))
{
    f(x);
}

template<typename Type> 
void apply2(Type x, Type (*f)(Type))
{
    f(x);
}

template<typename Type> 
void apply3(Type x, Type (&f)(Type))
{
    f(x);
}

int main(int argc, char *argv[])
{
    apply1(1,hello);
    apply2(2,hello);
    apply3(3,hello);
    return 0;
}

控除から生成される実際のasmは次のとおりです。

apply1

__Z6apply1IiEvT_PFS0_S0_E:
Leh_func_begin2:
    pushq   %rbp
Ltmp2:
    movq    %rsp, %rbp
Ltmp3:
    subq    $16, %rsp
Ltmp4:
    movl    %edi, -4(%rbp)
    movq    %rsi, -16(%rbp)
    movq    -16(%rbp), %rax
    movl    -4(%rbp), %ecx
    movl    %ecx, %edi
    callq   *%rax
    addq    $16, %rsp
    popq    %rbp
    ret
Leh_func_end2:

apply2

__Z6apply2IiEvT_PFS0_S0_E:
Leh_func_begin3:
    pushq   %rbp
Ltmp5:
    movq    %rsp, %rbp
Ltmp6:
    subq    $16, %rsp
Ltmp7:
    movl    %edi, -4(%rbp)
    movq    %rsi, -16(%rbp)
    movq    -16(%rbp), %rax
    movl    -4(%rbp), %ecx
    movl    %ecx, %edi
    callq   *%rax
    addq    $16, %rsp
    popq    %rbp
    ret
Leh_func_end3:

apply3

__Z6apply3IiEvT_RFS0_S0_E:
Leh_func_begin4:
    pushq   %rbp
Ltmp8:
    movq    %rsp, %rbp
Ltmp9:
    subq    $16, %rsp
Ltmp10:
    movl    %edi, -4(%rbp)
    movq    %rsi, -16(%rbp)
    movq    -16(%rbp), %rax
    movl    -4(%rbp), %ecx
    movl    %ecx, %edi
    callq   *%rax
    addq    $16, %rsp
    popq    %rbp
    ret
Leh_func_end4:

それらは同一です(私が思ったように)。私が何でも見ることができるという違いはありません。

注:コンパイラーがこれらの宣言を名前マングリング検査でどのように見たかについて言及する価値があります。

apply1: __Z6apply1IiEvT_PFS0_S0_E
apply2: __Z6apply2IiEvT_PFS0_S0_E
apply3: __Z6apply3IiEvT_RFS0_S0_E
于 2012-11-05T06:51:03.817 に答える
5
void apply1(double(f)(double));
void apply2(double(*f)(double));

これらの2つの関数は同一のシグネチャを持っているため、違いはありません。それらは両方とも関数パラメーターへのポインターを取ります。

ISO / IEC 14882:2011 8.3.5 [dcl.fct] / 5:

各パラメータのタイプを決定した後、「Tの配列」または「Tを返す関数」タイプのパラメータは、それぞれ「Tへのポインタ」または「Tを返す関数へのポインタ」に調整されます。

于 2012-11-05T07:28:40.083 に答える
1

私はあなたにそれをもっと単純に保つように努めます。小さなプログラムがあるとしましょう:

#include <stdio.h>
void my_int_func(int x)
{
    printf( "%d\n", x );
}

int main()
{
    void (*foo)(int);
    // the ampersand is actually optional
    foo = &my_int_func;

    return 0;
}

上記のように、整数を取り、voidを返す関数があります。main内で、my_int_funcを使用して関数ポインタfooを初期化しています。「アンパサンドはオプションです」というコメントを注意深く参照してください。実際には、すべてを示しています。アンパサンドを使用した場合とアンパサンドを使用しない場合の初期化に違いはありません。

したがって、両方のステートメントに違いはありません。

于 2012-11-05T07:02:51.697 に答える