如何避免单个派生 class 实现的虚函数?

How to avoid a virtual function for a single derived class implementation?

我有一个 Accessor class 定义我与其他 classes 和此 Accessor [=31= 中的多个基础 class 对象的接口] 实现各种风格的东西。

class Accessor
{
    std::shared_ptr<Base> object1;
    std::shared_ptr<Base> object2;
}

Accessor class 实现的当然不仅仅是对不同对象的调用,还有一个特定的函数,它确实只将函数调用重定向到一个对象。

现在的问题是,这个特定的函数应该只在一个派生的 class 中实现,当使用 Accessor class 调用函数时,众所周知特定对象始终是实现此方法的派生 class。目前,我正在定义一个 'empty' 基础 class 方法并仅在提到的派生 class.

中覆盖它

但是,我不喜欢这种方法,原因有二:首先,从设计的角度来看,它看起来很丑;其次,我还有一个 virtual 函数调用(这里讨论的函数在循环的热路径)。

总而言之,代码的最小版本如下所示:


class Base 
{ 
    virtual void f() const { std::cout << "Dummy implementation!\n";}
};
 
class Derived1 : Base
{
    void f() const override { std::cout << "Implementing the actual thing!\n";}
};

class Derived2 : Base
{
   // Carrying the dummy implementation of the base class
};

class Accessor
{
    std::shared_ptr<Base> object1;
    std::shared_ptr<Base> object2;

    // When calling this function, we know that we actually 
    // call object1 of type `Derived1`. Still, there might be 
    // cases where object1 has a different type, but then we don't 
    // call this function 
    void f() const { object1->f();}
}

整个描述可能在某种程度上表明整体设计不是最佳选择。不过,也许还有其他的选择可以重新设计这里的问题,让虚函数调用消失,代码更能反映实际情况。

如果您确定 object_1 始终指向 Derived1,则将其设为 std::shared_ptr<Derived1> object1。如果 Base::f() 的唯一目的是 Derived1,请将其设为非虚拟并将其从不需要它的 classes 中删除。

但如果其中一个假设不正确,请保持原样:

  • 在调用非虚函数之前进行额外的测试以检查class是否为Derived1并不比直接调用虚函数更有效。如果你不相信我,做一个基准。此外,如果将来您进一步专业化 Derived1,稍微不同 f(),您就不必担心了。

  • 假设它总是 Derived1 并且只是静态向下转换是有风险的。如前所述,如果您如此确定,只需将其声明为(转到第一段),并在初始化时进行(安全的)向下转换 object_1.

备注:虽然一开始有一个f()在大多数情况下是没用的,这看起来很奇怪,但它实际上是一个常见的在 tell, don't ask paradigm. A very typical example is when using the template method pattern.

中使用多态时的练习

你可以避免基class和虚方法std::variant,虽然它比虚函数调用慢一点。 您可以测试 Derived1 类型是否在 variant.

    class Derived1
    {
    public:
        void f(size_t i) { ++_i; }
    private:
        size_t _i {0};
    };

    class Derived2
    {
    public:
    };

    class Accessor
    {
    public:
        template<typename S>
        void add_one (std::shared_ptr<S> val) {
            object1 = val;
        }
        // When calling this function, we know that we actually
        // call object1 of type `Derived1`. Still, there might be
        // cases where object1 has a different type, but then we don't
        // call this function
        void f(size_t i) const {
            // you can check the type with this
            //if (std::holds_alternative<std::shared_ptr<Derived1>>(object1)) {
                std::get<std::shared_ptr<Derived1>>(object1)->f (i);
            //}
        }
    public:
        std::variant<std::shared_ptr<Derived1>, std::shared_ptr<Derived2>> object1;
    };

    int main ()
    {
        Accessor obj;
        obj.add_one (std::make_shared<Derived2>());
        return 0;
    }