寻找一种模式以避免在实现虚函数时使用dynamic_cast

时间:2016-07-09 19:09:32

标签: c++ virtual-functions dynamic-cast

我的问题是:我有一个带有几个具体分支类的接口根类。在应用程序代码中,存在一个指向根类的指针向量。我需要在几个地方循环遍历向量中的所有元素,并将它们与给定的实例进行比较:

// Application Code
void compare_loop(Root *r, std::vector<Root*> vec) {
    for (auto v : vec) {
        if (r->compare(v)) {
            // Do something to v
        }
    }
}

我最初的方法是在Root类中“比较”一个虚函数:

// Class Definition
class Root {
public:
    Root(double bar) : Bar(bar) {};

    virtual bool compare(Root *r) = 0;

protected:
    double Bar;
};

class BranchA : public Root {
public:
    BranchA(double bar, double baz) : Root(bar), BazA(baz) {};

    bool compare(Root *r) override;

protected:
    double BazA;
};

class BranchB : public Root {
public:
    BranchB(double bar, int baz, bool foo) : Root(bar), BazB(baz), Foo(foo) {};

    bool compare(Root *r) override;

protected:
    int BazB;
    bool Foo;
};

问题是,如果参数不是具有相同的具体类型,则“compare”函数的实现应始终求值为false,否则取决于BranchA / BranchB特有的成员变量。使用单个虚拟成员函数,我能想到的唯一方法是尝试使用dynamic_cast:

// Implementation
bool BranchA::compare(Root *r) {
    BranchA* branch = dynamic_cast<BranchA*>(r);

    if (branch == nullptr) {
        return false;
    }
    else {
        // Do some complicated comparison based on BranchA members
        return (Bar < branch->Bar) && (BazA < branch->BazA);
    }
}

bool BranchB::compare(Root *r) {
    BranchB* branch = dynamic_cast<BranchB*>(r);

    if (branch == nullptr) {
        return false;
    }
    else {
        // Do some complicated comparison based on BranchB members
        return (Bar > branch->Bar) && (BazB > branch->BazB) && (Foo == branch->Foo);
    }
}

这对我来说似乎不是最优雅的方法,但我不确定。我想知道在类定义和实现中是否有一种不同的方法可以在不改变应用程序代码的情况下产生相同的结果。或者,这是一个使用dynamic_cast的实例吗?

4 个答案:

答案 0 :(得分:7)

我通常使用一种使用NVI习语 1,2 的模式。仍然需要演员,但它是static_cast而不是dynamic_caststatic_cast避免了与dynamic_cast相关的额外费用,并且保证安全(请参阅代码注释)。

但是,我并不是说这个解决方案比OP的代码更快,因为它仍然使用typeid检查以及isEqual的动态调度功能

此问题中代码的主要优点是基类的比较逻辑中的更改不会影响派生类的实现,其中可能有很多。

示例代码

#include <iostream>
#include <memory>
#include <vector>

class Root
{
public:
    explicit Root(double bar) : Bar(bar) {}

    // Base class must have a virtual destructor for deletion through
    // the base pointer to work properly
    virtual ~Root() {}

    bool operator==(const Root& other) const
    {
        // Make sure the two types being compared are the same derived type
        return (typeid(*this) == typeid(other)) &&
            // Compare all state associated with the base class
            (Bar == other.Bar) &&
            // Dispatch comparison to the derived implementation to finish
            // the comparison
            isEqual(other);
    }

private:
    // Guaranteed to only be dispatched by operator== if 'other' is the
    // same type as '*this'
    virtual bool isEqual(const Root &other) const = 0;

    double Bar;
};

class BranchA : public Root
{
public:
    BranchA(double bar, double baz) : Root(bar), BazA(baz) {}

private:
    virtual bool isEqual(const Root& other) const override
    {
        // static_cast is safe since the Base class guarantees it won't
        // call this function unless 'other' and '*this' are the same type
        const BranchA& branch = static_cast<const BranchA&>(other);
        return (BazA == branch.BazA);
    }

    double BazA;
};

class BranchB : public Root
{
public:
    BranchB(double bar, int baz, bool foo) : Root(bar), BazB(baz), Foo(foo) {}

private:
    virtual bool isEqual(const Root& other) const override
    {
        // static_cast is safe since the Base class guarantees it won't
        // call this function unless 'other' and '*this' are the same type
        const BranchB& branch = static_cast<const BranchB&>(other);
        return (BazB == branch.BazB) && (Foo == branch.Foo);
    }

    int BazB;
    bool Foo;
};

void compare_loop(const Root &r, const std::vector<std::unique_ptr<Root>>& vec)
{
    for (const auto& v : vec)
    {
        if (r == *v)
        {
            std::cout << "Equivalent\n";
        }
        else
        {
            std::cout << "Different\n";
        }
    }
}

int main()
{
    BranchA root(1.0, 2.0);

    std::vector<std::unique_ptr<Root>> branches;
    branches.push_back(std::make_unique<BranchA>(root));
    branches.push_back(std::make_unique<BranchA>(1.0, 1.0));
    branches.push_back(std::make_unique<BranchB>(1.0, 2.0, true));

    compare_loop(root, branches);

    return 0;
}

示例输出

Equivalent
Different
Different

Live Example

1 Non-virtual interface pattern - Wikipedia
2 Virtuality - Herb Sutter

答案 1 :(得分:6)

Bjarne Stroustrup有一篇很好的出版物,关于如何生成与dynamic_cast<>相当的东西,可以像模数一样快。它可以在这里找到:http://www.stroustrup.com/fast_dynamic_casting.pdf

基本思想是,为每个类分配一对int。第一个是每个类的不同质数,第二个是每个基类的第二个乘以当前类的第一个的乘积。如果source::second % target::first == 0dynamic_cast<>将成功。它对许多事情都有好处:快速dynamic_cast<>,钻石检测,一组元素的最常见子类型,抽象访问者和多次派遣等。

答案 2 :(得分:0)

一个简单的替代方法就是让每个派生类都提供自己的类型标识符并进行比较。

#include <iostream>
#include <fstream>
#include <string>

class Base {
    virtual const char* type_name() const noexcept = 0;
public:
    Base() {}
    bool is_same_type(const Base* rhs) const noexcept {
        return type_name() == rhs->type_name();
    }
};

class D1 : public Base {
    const char* type_name() const noexcept override { return "D1"; }
public:
    D1() {}
};

class D1D1 : public D1 {
    const char* type_name() const noexcept override { return "D1D1"; }
public:
    D1D1() {}
};

void test(const Base* lhs, const Base* rhs)
{
    std::cout << lhs->is_same_type(rhs) << '\n';
}

int main()
{
    D1 d1;
    D1D1 d1d1;
    test(&d1, &d1d1);
}

缺点是,如果我忘记在D1D1中明确添加覆盖,它将继承它的父母type_name并且声称自己是同一类型。

答案 3 :(得分:0)

您在LSP失败,基类的属性可以通过基类的属性来描述。

实际上,您的界面&#34;表面&#34;是一个动态的,无限扩展到基类的所有可能的派生类型。例如,有人不能创建一个独立的克隆&#34;您的一种类型:基类的确切动态类型是其行为的一部分。

有许多替代方法。

您可以在(可能是活动的)动态字典中存储和公开属性,并进行比较。

您可以有一个有限的子类型枚举,并明确地显示其接口的基本部分的子类型,以及快速转换接口。

你可以从COM窃取一个页面,并有一个查询接口,其引用计数允许第三方克隆。然后,类型可以通过模仿该接口来模仿派生类,而不是实现。

或者您可以接受您的界面表面无界限和指定不良。继续使用动态强制转换或typeid(基本等效)。

如果您想进一步研究,问题的名称是双重调度问题或双重动态调度问题。