【问题标题】:Automatically downcast function arguments in C++在 C++ 中自动向下转换函数参数
【发布时间】:2018-12-17 17:51:30
【问题描述】:

我有两个类,比如说 Base 和 Derived:

class Base {
public:
    virtual ~Base() = 0;
};
class Derived : public Base {};

还有一个函数 foo:

auto foo (Derived* d) {
    ...
}

是否可以自动向下转换其参数?所以我可以这样做:

Base* b = new Derived();
foo(b);

基本上我想写这个而不在函数调用之前显式转换它。 我读过一些关于转换运算符/构造函数的东西,但它们在我的情况下似乎没有用,你还有其他想法吗?

编辑:抱歉,我用 2 个类和一个函数过度简化了这个问题。但实际上我有一个包含 50 个函数和 3 个类(一个超类和 2 个子类)的库。不幸的是,这使得最简单和最干净的解决方案不合适,因为在我看来(如果我错了,请纠正我)它们的规模很差。

【问题讨论】:

标签: c++ inheritance casting


【解决方案1】:

根据您的需要,我可以想到三种可能的解决方案。在我的示例中,我已将原始指针替换为 unique_ptrs。

案例 1:您不需要每个派生类型的基类型相同。

使用CRTP 允许基类型将自身作为派生类型调用。示例实现:

template <typename DerivedType>
class Base {
    template <typename F>
    auto invoke_as_derived(F&& f) {
        return std::forward<F>(f)(static_cast<DerivedType*>(this));
    }
};
class Derived : public Base<DerivedType> {};

用法:

std::unique_ptr<Base<Derived>> b = std::make_unique<Derived>();
b->invoke_as_derived(foo);

由于您提到使用基指针列表,这可能对您不起作用。

案例 2:您需要一个共享的基类型,但在您的类型层次结构中只有一层,并且没有虚拟方法。

使用std::variantstd::visit

class Derived {};
using Base = std::variant<Derived, /* other derived types */>;

auto foo(Derived*) { ... }

class FooCaller {
    operator ()(Derived& d) {
        return foo(&d);
    }
    // Overload for each derived type.
}

用法:

Base b = Derived();
std::visit(FooCaller{}, b);

案例 3:您需要一个基本类型,但还需要虚拟方法和/或类型层次结构中的其他层。

您可以试试visitor pattern。它需要一些样板文件,但根据您的需要,它可能是最佳解决方案。实现草图:

class Visitor; // Forward declare visitor.

class Base
{
public:
    virtual void accept(Visitor& v) = 0;
};
class Derived : public Base
{
public:
    void accept(Visitor& v) final { v.visit(*this); }
};

struct Visitor
{
    virtual void visit(Derived&) = 0;
    // One visit method per derived type...
};
struct FooCaller : public Visitor
{
    // Store return value of call to foo in a class member.
    decltype(foo(new Derived())) return_value;

    virtual void visit(Derived& d)
    {
        return_value = foo(&d);
    }
    // Override other methods...
};

用法:

std::unique_ptr<Base> b = std::make_unique<Derived>();
FooCaller foo_caller;
b->accept(foo_caller);

您可以编写一个访问者,它将一个函数应用于元素,这样您就不必为所有函数重复此操作。或者,如果您可以更改函数本身,则可以将您的函数替换为访问者类型。

编辑:将调用语法简化为foo(b)

为每个要向其传递Base 对象的函数重载集定义一个重载。示例,使用第三种技术:

auto foo(Base* b) {
    FooCaller foo_caller;
    b->accept(foo_caller);
    return std::move(foo_caller.return_value);
}

现在foo(b.get()) 将在运行时委托给foo 的适当重载。

【讨论】:

  • 感谢您的详细回答,尽管这并不能完全解决我的问题。也许我应该更加强调这样一个事实,即我的目标是在不添加更多逻辑的情况下进行像foo(b) 这样的普通函数调用。否则我只会做foo(dynamic_cast&lt;Derived*&gt;(b))。无论如何,既然你提出了我不知道的不同解决方案,并且可能/部分可行,我会接受你的回答
  • 如果调用站点的语法非常重要,您可以为每个函数名称定义一个重载,采用 Base 指针/变量,然后使用上述之一委托给内部的真正重载技巧。我将在我的答案中编辑一个示例。
  • 伟大而完整的答案....但是这个人可能只是在学习 C++,只需要一个虚拟成员函数。通常关于 SO 的问题实际上是 XY 问题。
  • 你知道,CRTP 可以派生自一个通用的基本类型……而class FooCaller(Base) 是什么?
  • 糟糕,复制粘贴错误。固定的。你是对的 - 第一种和第三种方法可以结合使用以减少样板代码,同时保持通用的基本类型。
【解决方案2】:

通常的方法不是向下转换,而是使用虚函数。 IE。将void foo() 放入类中。

#include<iostream>
class Base {
public:
    virtual ~Base() = default;
    virtual void foo() { std::cout << "Base foo()\n"; }
};
class Derived : public Base {
public:
    void foo() override { std::cout << "Derived foo()\n"; }
};

int main()
{
    Base* b = new Derived();
    b->foo();
    delete b;
}

输出:

Derived foo()

如果想让Base::foo()无法调用,可以设置

class Base {
public:
    virtual ~Base() = default;
    virtual void foo() = 0;
};

使Base 成为一个抽象类。

但是如果你真的想调用foo(b),你可以使用一个(模板化的)辅助函数。例如:

#include<iostream>
class Base {
public:
    virtual ~Base() = default;
    virtual void foo() = 0;
};
class Derived : public Base {
public:
    void foo() override {
        std::cout << "Derived foo()\n";
    }
};

template<typename T>
void foo(T* t)
{
    t->foo();
}

int main()
{
    Base* b = new Derived();
    foo(b);
    delete b;
}

【讨论】:

  • 谢谢,但正如我在之前的 cmets 中所说,我有 50 个或更多的函数需要像这样处理,还有 2 个派生类。所以我猜这种方法会很糟糕
  • @Ale 我没有看到 cmets。您应该在问题中添加该信息。您需要向我们提供完整的信息,否则我们只是在这里猜测......
  • 它在来自问题的 cmets 部分的聊天中,以回应另一个已经建议您的解决方案的用户。不过,我会将这些额外信息添加到问题中
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-09
  • 1970-01-01
  • 2017-08-15
  • 1970-01-01
  • 2014-03-17
  • 1970-01-01
相关资源
最近更新 更多