【问题标题】:Base class is an ambiguous base of derived class基类是派生类的模棱两可的基类
【发布时间】:2022-01-23 00:17:25
【问题描述】:

我无法理解在多重继承情况下如何区分基类的多个实例。在寻找解决方案时,我只找到了关于 virtual inheritance 的答案,但这根本不能解决我的问题,因为我不想在继承树末尾的最终类中拥有单个基类实例。

在维基百科上,第一段指出:

如果没有虚拟继承,如果两个类 B 和 C 继承自类 A,而类 D 继承自 B 和 C,那么 D 将包含 A 的成员变量的两个副本:一个通过 B,一个通过 C。这些将可以使用范围解析独立访问。

但我没有找到任何关于如何在我的情况下使用范围解析的信息。

至于我的任务,(强制和过于复杂的)类层次结构是:

class Vehicle
{
    protected:
        unsigned long uPrice;
        char* szOwner; // for some reason, not allowed to use std::string in my homework...
    public:
        unsigned long GetPrice();
        // ...
}

class Trailer : public Vehicle
{
    protected:
        unsigned long uWid;
        unsigned long uHei;
        unsigned long uLen;
        unsigned long uMaxWeight;
    public:
        // ...
};

class Car : public Vehicle
{
    protected:
        unsigned long uWid;
        unsigned long uHei;
        unsigned long uLen;
        char* szBrand;
    public:
        // ...
};

class Trailer_Car : public Trailer, public Car
{
    private:
        unsigned long uTotalMass;
    public:
        // ...
};

如上所述,我希望Trailer_Car 的一个实例中有多个Vehicle 实例(一个用于Car,一个用于Trailer)。这完全适用于:

Trailer_Car c(/*...*/, 3500, 1200);
std::cout << c.Trailer_Car::Car::GetPrice() << "\n"; // prints 3500
std::cout << c.Trailer_Car::Trailer::GetPrice();  // prints 1200

但是,在我的代码中,我必须对一个不均匀的数组(可以包含 4 个类中的任何一个)进行排序,并将Trailer_Car 转换为Vehicle 会导致error: 'Vehicle' is an ambiguous base of 'Trailer_Car'。示例:

Vehicle** Arr = new Vehicle*[N];
// ...

Arr[i] = static_cast<Vehicle*>(new Trailer_Car); // error: 'Vehicle' is an ambiguous base of 'Trailer_Car'

我该如何解决这个问题?我知道错误来自这样一个事实,即Arr[i] 不知道从Trailer_Car 指向哪个Vehicle,但仍然没有想到C++。
由于我更习惯于 C,所以我只会将 Arr 设为 void**,尽管我不知道 C++ 中的做法有多好,我要求这样做以避免在 C++ 中使用 C。

【问题讨论】:

  • 为什么不希望在继承树末尾的最终类中拥有单个基类实例? Trailer_Car 派生自 2 个基本 Vehicle 类是什么意思?
  • @drescherjm 遗憾的是,没有,虚拟继承将生成基类的单个实例,我需要两个(一个存储预告片的价格和所有者,一个存储价格和所有者汽车)
  • 您确实意识到,如果没有虚拟析构函数,您将无法正确释放任何继承自 Vehicle 的类,仅给出指向 Vehicle 的指针。 Trailer_Car 的一个更“自然”的替代方案是 CombinedVehicle 拥有 2 辆(甚至更多)车辆并使用这些来确定它所代表的车辆的属性(例如,使用零件价格的总和作为总和等)。至于访问权限:您可以向上转换到不再模棱两可的程度,但正如所说的那样,这不是一个好主意Arr[i] = static_cast&lt;Trailer*&gt;(new Trailer_Car);
  • @fabian 是的,我知道,我永远不会设计这样的课程,但功课就是功课 :( 我不能随意修改课程。

标签: c++ inheritance


【解决方案1】:

您可以使用指针类型的中间转换来控制您获得重复基类的哪个实例

Arr[i] = upcast<Car*>(new Trailer_Car);

Arr[i] = upcast<Trailer*>(new Trailer_Car);

我建议避免对应该是隐式转换的东西进行“真正的演员表”。它不会强迫编译器做一些愚蠢的事情然后闭嘴,就像演员通常会做的那样。

template<typename S, typename T> S upcast(const T& t) { return t; }

通过这种方式,这两行中的任何一条都为您提供了具有显式指定目标类型的隐式转换,然后是具有推断目标类型 (Vehicle*) 的隐式转换。

请注意,在存储指向两个Vehicle 子对象中的一个或另一个的过程中,您还要选择排序逻辑将使用哪些数据。

此外,您不应使用newdelete 手动管理动态内存,而应使用智能指针。您编写的一个或一个库...但遵循单一职责原则,不要将内存管理与其他任何东西放在同一类中。

【讨论】:

  • 这绝对是一个进步,但是,如果Arr[i] = upcast&lt;Trailer*&gt;(new Trailer_Car); 我应该如何安全地访问Car* 部分?我对数组进行排序所拥有的只是Vehicle** 数组(我知道数组中任何给定索引处的类型,因此我知道如何调用GetPrice)。尽管如此,唯一想到的是将 Arr 设为 void** 并进行强制转换并手动调用析构函数。
  • @Tudor:如果您知道在TrailerTrailer 子对象中有一个指向Vehicle 的指针,您可以使用一系列转换来返回。 static_cast&lt;Trailer_Car*&gt;(static_cast&lt;Trailer*&gt;(Arr[i]))。请注意,这一次您需要“真正的演员表”,编译器没有保留足够的信息来了解这是否安全。如果你有任何虚成员函数,你可以使用dynamic_cast而不是static_cast(仍然分两步)编译器会检查实际对象的运行时类型。
  • 但是向下转换是一种巨大的代码气味。也许你想要一个接口,它是一个只有纯成员函数的虚拟基类。 struct IHasTotalPrice { virtual unsigned long TotalPrice() = 0; }; unsigned long Vehicle::TotalPrice { return GetPrice(); } unsigned long Trailer_Car::TotalPrice() { return Car::GetPrice() + Trailer::GetPrice(); } 现在你的数组可以包含 IHasTotalPrice* 并且排序算法不需要做任何向下转换。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-01
  • 2023-03-19
  • 2011-09-07
  • 1970-01-01
  • 2016-02-23
  • 2017-06-12
相关资源
最近更新 更多