【问题标题】:How to handle passing an array of derived class as argument in a function that accepts array of base class?如何处理在接受基类数组的函数中传递派生类数组作为参数?
【发布时间】:2016-09-20 10:12:51
【问题描述】:

我有以下带有 2 个类的示例代码:

class B {
public:
    int b;
};

class D : public B {
public:
    int d;
};

我有一个函数,它接受 B 作为数组并对 b 成员求和:

int sum(B a[], int count){

    int sum = 0;

    for(int i = 0; i < count; i++) {
        sum += a[i].b;
    }

    return sum;
}

这是我的主要内容:

int main() {

    D* a = new D[3];

    for(int i = 0; i < 3; i++)
        a[i].b = i+1;

    cout << "Sum is " << sum(a, 3) << endl;
    return 0;
}

我所知道的是,由于切片,这段代码不起作用。我没有得到正确的输出,应该是 6。我试图在 sum 函数中得到参数:

int sum(B& a[], int count)

通过引用传递a,但这会导致编译错误,因为您不能使用引用数组。

我的问题是最好的方法是什么?

目前我的解决方案是将我的数组声明为:

B* a = new D[3];

但尚不完全清楚为什么它不能很好地与以前的实现配合使用。

【问题讨论】:

  • 使用virtualDstd::vector&lt;std::unique_ptr&lt;B&gt;&gt;
  • 你能解释一下为什么要让 D 类虚拟化吗?我也在寻找一种不使用 vector 或 unique_ptr 的解决方案,但感谢您的回答
  • B一个virtual析构函数。这就是所谓的运行时多态性。
  • “我正在寻找一种不使用矢量或 unique_ptr 的解决方案”。您可以改用原始数组和原始指针,但不建议这样做。您不能将派生类对象数组传递给接受基类对象数组的函数。这是不可能的。

标签: c++ arrays polymorphism pass-by-reference slice


【解决方案1】:

D 数组永远不会看起来像B 数组,因为这些类型具有不同的大小。 D 的大小比B 大,因此D 数组中的连续元素将比B 数组中的连续元素间隔更远(因为每个元素占用更多空间)。

如果你想让子类型化多态起作用,你需要使用指针(或引用),而不是对象类型本身。你不能有一个引用数组,所以你必须使用一个指针数组。

这样的事情会起作用:

int sum(B *a[], int count) {

    int sum = 0;

    for(int i = 0; i < count; i++) {
        sum += a[i]->b;
    }

    return sum;
}

int main() {
    D *a[3];

    for (int i = 0; i < 3; i++) {
        a[i] = new D;
        a[i]->b = i+1;
    }

    cout << "Sum is " << sum((B **)a, 3) << endl;

    for (int i = 0; i < 3; i++) {
      delete a[i];
    }
    return 0;
}

请注意,我仍然必须从 D ** 转换为 B **,因为这种转换仍然可能导致潜在的不安全内容。如果您已将数组声明为 B *[3],则不需要强制转换:

int sum(B *a[], int count) {

    int sum = 0;

    for(int i = 0; i < count; i++) {
        sum += a[i]->b;
    }

    return sum;
}

int main() {
    B *a[3];

    for (int i = 0; i < 3; i++) {
        a[i] = new D;
        a[i]->b = i+1;
    }

    cout << "Sum is " << sum(a, 3) << endl;

    for (int i = 0; i < 3; i++) {
      delete a[i];
    }
    return 0;
}

或者你可以使用模板:

template <class T>
int sum(T *a[], int count) {

    int sum = 0;

    for(int i = 0; i < count; i++) {
        sum += a[i]->b;
    }

    return sum;
}

int main() {
    D *a[3];

    for (int i = 0; i < 3; i++) {
        a[i] = new D;
        a[i]->b = i+1;
    }

    cout << "Sum is " << sum(a, 3) << endl;

    for (int i = 0; i < 3; i++) {
      delete a[i];
    }
    return 0;
}

模板也适用于您原来的非指针方式:

template <class T>
int sum(T a[], int count) {

    int sum = 0;

    for(int i = 0; i < count; i++) {
        sum += a[i].b;
    }

    return sum;
}

int main() {
    D a[3];

    for (int i = 0; i < 3; i++) {
        a[i].b = i+1;
    }

    cout << "Sum is " << sum(a, 3) << endl;
    return 0;
}

【讨论】:

  • sum((B **)a, 3) 这只是未定义的行为。
【解决方案2】:

您提出的解决方案实际上是正确的,但您可能希望使用 virtual 关键字使 B 抽象。 另一种选择是重新定义 sum 函数并使其接受 D 对象。

解释为什么您的原始实现不起作用的直观方法如下:

在 sum 的主体中,我们有这行代码:

sum += a[i].b; 其中 a 是指向 B 类型对象的指针。

这意味着为了获取索引i处的元素,我们需要添加 i * sizeof(B) 到基地址 a。然而这个地址现在错了,我们需要地址i * sizeof(D)

通过将指针类型声明为 B,此问题已得到解决。

顺便说一句,您可能还想让 b 变量受保护而 d 为私有,然后提供 getter。

【讨论】:

    猜你喜欢
    • 2021-09-01
    • 2017-01-08
    • 2013-08-02
    • 2012-02-07
    • 1970-01-01
    • 1970-01-01
    • 2016-03-04
    • 2015-08-20
    • 2011-06-03
    相关资源
    最近更新 更多