代码之家  ›  专栏  ›  技术社区  ›  R zu

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

  •  1
  • R zu  · 技术社区  · 7 年前

    我想写一个类 A 有两种方法 b c 。 方法 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();
    }
    

    结果:

    1
    

    不是2。

    编辑:我想指定 C 在编译时执行。有多种形式的 C 我想插进同一个 B 方法

    4 回复  |  直到 7 年前
        1
  •  3
  •   SergeyA    7 年前

    虚拟函数和函数指针都有相同的性能问题。

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

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

        2
  •  1
  •   gflegar    7 年前

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

    template <typename Derived>
    class A { 
    public:
        A() {}
        void b() { static_cast<Derived*>(this)->c(); }
        void c() { std::cout << "1\n"; }
    };
    
    class B : public A<B> { 
    public:
        void c() { std::cout << "2\n"; }
    };
    
    int main() {
        B b;
        b.b();
    }
    

    现场示例: https://onlinegdb.com/SJzxbtqjz

    然而,这可能不是您想要的-这完全取决于您的用例,我无法从您的问题中判断出来。确保您阅读了我上面链接的维基百科页面上的陷阱部分:

    静态多态性的一个问题是,没有使用 基类类似于上面示例中的“Shape”,派生类不能 因为每个CRTP基类都是唯一的类型,所以可以均匀地存储。对于 因此,从共享基类继承更为常见 使用虚拟析构函数,如上面的示例。

    编辑:

    否则,您将需要按照您的建议进行操作,并付出通过函数指针的代价。然而,如果您调用的函数不是很简单,那么您可能不会看到性能上的差异。

    编辑2:

    编译器将完全内联调用 c() 进行适当的优化。

    GCC 7.3带有-O2优化标志,代码更简单(只是为了避免 std::cout ):

    template <typename Derived>
    class A { 
    public:
        A() {}
        int b() { return static_cast<Derived*>(this)->c(); }
        int c() { return 1; }
    };
    
    class B : public A<B> { 
    public:
        int c() { return 2; }
    };
    
    
    int f() {
        B b;
        return b.b();
    }
    

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

    f():
      mov eax, 2
      ret
    

    现场示例: https://godbolt.org/g/Mh99bZ

        3
  •  0
  •   R zu    7 年前

    模板解决方案:

    #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();
    }
    

    结果:

    1
    2
    
        4
  •  0
  •   R zu    7 年前

    带参考和模板的解决方案。

    它比模板更好,因为方法 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();
    }
    

    结果:

    X1: 10