C++ 使用多次调用的可替换方法初始化

C++ 使用多次调用的可替换方法初始化,c++,performance,function,oop,polymorphism,C++,Performance,Function,Oop,Polymorphism,我想用两种方法b和c编写一个类a。 方法b调用c。 我想用我喜欢的任何东西来代替c 如果我把c做成一个函数指针或虚拟方法,我想我可以做到这一点 但是,b将被多次调用。这两种解决方案会影响性能吗 有没有可能重新设计它以避免使用函数指针/虚拟方法 测试继承: #include <iostream> class A { public: A() {} void b() { c(); } void c() { std::cout << "1\n"; } }

我想用两种方法
b
c
编写一个类
a
。 方法
b
调用
c
。 我想用我喜欢的任何东西来代替
c

如果我把
c
做成一个函数指针或虚拟方法,我想我可以做到这一点

但是,
b
将被多次调用。这两种解决方案会影响性能吗

有没有可能重新设计它以避免使用函数指针/虚拟方法

测试继承:

#include <iostream>
class A { 
public:
    A() {}
    void b() { c(); }
    void c() { std::cout << "1\n"; }
};

class B : public A { 
public:
    void c() { std::cout << "2\n"; }
};

int main() {
    B b;
    b.b();
}
不是2


编辑:我想指定
c
在编译时做什么。我想将多种形式的
c
插入到相同的
b
方法中。

虚拟函数和函数指针都存在相同的性能问题

首先,编译器通常无法内联这些调用。一般来说,编译器将不得不进行真正的函数调用并承担副作用,因此许多优化技术无法执行。通常影响是相当大的


其次,在代码执行期间,CPU通常无法通过指针调用预取代码,因为它不知道执行将转移到哪里。然而,与分支预测器类似,硬件调用设备化器尝试根据过去的性能推测性地预取指令,并且在多次调用之后,它们经过培训并在那里做得很好。但是,缺少内联仍然会让您头疼。

虚拟函数和函数指针都面临相同的性能问题

首先,编译器通常无法内联这些调用。一般来说,编译器将不得不进行真正的函数调用并承担副作用,因此许多优化技术无法执行。通常影响是相当大的


其次,在代码执行期间,CPU通常无法通过指针调用预取代码,因为它不知道执行将转移到哪里。然而,与分支预测器类似,硬件调用设备化器尝试根据过去的性能推测性地预取指令,并且在多次调用之后,它们经过培训并在那里做得很好。但是,缺少内联仍然会困扰您。

如果您不希望性能受到影响,可以使用:

将不为类
B
生成代码,并为
f
生成以下程序集:

f():
  mov eax, 2
  ret

现场示例:

如果您不想受到性能处罚,可以使用:

将不为类
B
生成代码,并为
f
生成以下程序集:

f():
  mov eax, 2
  ret
实例:

模板解决方案:

#include <iostream>

struct X1 {
    static void c() { std::cout << "1\n"; }
};

struct X2 {
    static void c() { std::cout << "2\n"; }
};

template <class X>
class A {
public:
    A() {}
    void b() { X::c(); }
};

int main() {
    A<X1> a;
    a.b();
    A<X2> b;
    b.b();
}
模板解决方案:

#include <iostream>

struct X1 {
    static void c() { std::cout << "1\n"; }
};

struct X2 {
    static void c() { std::cout << "2\n"; }
};

template <class X>
class A {
public:
    A() {}
    void b() { X::c(); }
};

int main() {
    A<X1> a;
    a.b();
    A<X2> b;
    b.b();
}

具有参考和模板的解决方案

它比模板更好,因为方法
c
可以使用类
X1
中的属性

#include <iostream>
class X1 {
public:
    X1(int v) { _v = v; }
    void c() { 
        std::cout << "X1: " << _v << "\n"; 
    }
    int _v;
};

template <typename X>
class A {
public:
    A(X& x) : _x(x) {}
    void b() { _x.c(); }
private:
    X& _x;
};
template <typename X>
A<X> make_A(X& x) { return A<X>(x); }
int main() {
    X1 x1(10);
    // A a1(x1);  ///< need c++17
    auto a = make_A(x1);  ///< works for c++11
    a.b();
}

具有参考和模板的解决方案

它比模板更好,因为方法
c
可以使用类
X1
中的属性

#include <iostream>
class X1 {
public:
    X1(int v) { _v = v; }
    void c() { 
        std::cout << "X1: " << _v << "\n"; 
    }
    int _v;
};

template <typename X>
class A {
public:
    A(X& x) : _x(x) {}
    void b() { _x.c(); }
private:
    X& _x;
};
template <typename X>
A<X> make_A(X& x) { return A<X>(x); }
int main() {
    X1 x1(10);
    // A a1(x1);  ///< need c++17
    auto a = make_A(x1);  ///< works for c++11
    a.b();
}


std::sort
可以将比较函数作为参数。那个参数也是函数指针吗?不,std::sort是一个模板函数。它的参数可以是函数指针,但也可以是函子或lambda,并在编译时解析。也许我可以提供一个额外的模板参数C。C可以使用静态方法接收类。而
A::b()
将调用
C::C()
。我记得google sparsehash采用了一个类似于哈希函数的模板参数。最基本的是,如果你想在运行时灵活地调用不同的函数,编译器需要灵活的运行时机制来区分调用哪个函数。因此,无论发生什么,您都必须支付与虚拟调用或函数指针取消引用相同的惩罚。编译器在其虚拟函数表中的工作可能比你拼凑起来试图击败它要好。嗯。如果选择在编译时确定,我很好。我想我会测试class+静态方法,看看它是否有效。
std::sort
可以将比较函数作为参数。那个参数也是函数指针吗?不,std::sort是一个模板函数。它的参数可以是函数指针,但也可以是函子或lambda,并在编译时解析。也许我可以提供一个额外的模板参数C。C可以使用静态方法接收类。而
A::b()
将调用
C::C()
。我记得google sparsehash采用了一个类似于哈希函数的模板参数。最基本的是,如果你想在运行时灵活地调用不同的函数,编译器需要灵活的运行时机制来区分调用哪个函数。因此,无论发生什么,您都必须支付与虚拟调用或函数指针取消引用相同的惩罚。编译器在其虚拟函数表中的工作可能比你拼凑起来试图击败它要好。嗯。如果选择在编译时确定,我很好。我想我会测试class+静态方法,看看它是否有效。没有函数指针,没有虚方法,也没有宏。希望不要太难看。没有函数指针,没有虚方法,也没有宏。希望不会太难看。它似乎有一个间接的方向:
static_cast(this)->c()。这会影响性能吗?不,编译器可以对此进行优化。我更新了我的答案以反映这一点。它似乎有一个间接的方向:
static_cast(this)->c()。这会影响性能吗?不,编译器可以对此进行优化。我更新了我的答案以反映这一点。实际的类有两个可重放的方法。最后我选择了CRTP。实际的类有两个可重放的方法。所以我最后选择了CRTP。
#include <iostream>
class X1 {
public:
    X1(int v) { _v = v; }
    void c() { 
        std::cout << "X1: " << _v << "\n"; 
    }
    int _v;
};

template <typename X>
class A {
public:
    A(X& x) : _x(x) {}
    void b() { _x.c(); }
private:
    X& _x;
};
template <typename X>
A<X> make_A(X& x) { return A<X>(x); }
int main() {
    X1 x1(10);
    // A a1(x1);  ///< need c++17
    auto a = make_A(x1);  ///< works for c++11
    a.b();
}
X1: 10