C++以多态方式处理数组可能会遇到的问题

今天读《More Effective C++》时遇到一个条款:绝对不要以多态方式处理数组。以前自己也没有注意过,觉得有必要记录下来。

C++是允许通过base class的指针或引用来操作derived class所形成的数组的。但发生的事情可能会令你感到意外。下面举例说明:

基类和派生类是这样的:

class BST                            /*base class*/
{
public:
    BST() : x1(1) {}
    virtual ~BST()
    {
        cout << "Good Bye BST." << endl;
    }
    int x1;
};

class BalancedBST : public BST        /*derived class*/
{
public:
    BalancedBST() : BST(), x2(2) {}
    virtual ~BalancedBST()
    {
        cout << "Good Bye BalancedBST." << endl;
    }
    int x2;
};

下面我重载了两个输出操作符:

/*输出base class*/
ostream& operator<<(ostream& os, const BST& obj)
{
    os << "class BST: " << obj.x1 << endl;
    return os;
}

/*输出derived class*/
ostream& operator<<(ostream& os, const BalancedBST& obj)
{
    os << "Class BalancedBST: " << obj.x1 << ‘ ‘ << obj.x2 << endl;
    return os;
}

下面这个函数用于输出base class和derived class的数组。

/*输出base class和derived class数组*/
void Print(ostream& os, const BST arr[], int n)
{
    for (int i = 0; i < n; ++i)
    {
        os << arr[i];
    }
}

当以如下方式测试时,没有问题。

BST baseArr[10];
Print(cout, baseArr, 10);    //好的,没问题,正常

当以如下方式测试时,就会出现问题。

BalancedBST deriveArr[10];
Print(cout, deriveArr, 10);    //出错啦

编译器要想遍历数组中每一个元素,它必须知道每一个元素的大小。很明显,当print参数为BalancedBST数组时,编译器静态的将其数组大小当作BST的大小处理,以*(i+arr)的方式前进,结果是未知的。



还有一种情况,就是通过一个base class指针,删除一个由derived class组成的数组。

当以如下方式测试时,没有问题。

BST *base = new BST[10];
delete [] base;                    //好的,没有问题

BalancedBST *derived = new BalancedBST[10];
delete [] derived;                //好的,没有问题

当我以如下方式测试时,就会有问题。

当数组被删除时,数组中每个元素的destructor会被调用,调用的顺序与构造顺序相反。也就是说执行delete [] base语句时,会产生类似下面的代码。

for (int i = 9; i >= 0; --i)    //编译器产生类似的代码,但是是错误的。
{
    base[i].BST::~BST();
}

根本原因还是编译器把derived class数组成员的大小当作base class来计算数组元素的位置。

C++规定,通过base class指针删除一个由derived class objects构成的数组,其结果是未定义的。所以,多态和指针算术不能混用,数组对象几乎总会涉及指针的算术运算,因而数组和多态不要混用。

时间: 2025-01-16 19:45:08

C++以多态方式处理数组可能会遇到的问题的相关文章

【More Effective C++ 条款3】最好不要以多态方式处理数组

1.在数组与多态混用的情况下,数组元素的访问会出现不可预期的结果(因为数组元素的访问会使用到下标运算) 将一个子类对象数组传递给一个父类对象数组声明的函数,编译器会允许这个行为,但是由于子类对象和父类对象的内存结构不同,会导致运行结果异常,因为在这种情况下,编译器仍然假设每一个元素的大小是父类对象元素的大小,但此时实际上每一个元素的大小是子类对象元素的大小 #include<bits/stdc++.h> using namespace std; class BST { public: char

10.2.3.1 以函数方式使用数组

我们先来看一个 F# 的例子,这是两个F# 库处理数组的重要的高阶函数,然后,用 C# 实现相同的功能.清单 10.12 的中脚本,先用随机数初始化一个数组,然后,计算出它们的平方. 清单 10.12 处理数组的函数式方法(F# Interactive) > let rnd = new System.Random();; val rnd : System.Random > let numbers = Array.init 5 (fun _-> rnd.Next(10));;   [1]

在ASP.NET MVC中以post方式传递数组参数的示例

最近在工作中用到了在ASP.NET MVC中以post方式传递数组参数的情况,记录下来,以供参考. 一.准备参数对象 在本例中,我会传递两个数组参数:一个字符串数组,一个自定义对象数组.这个自定义对象UserInfo定义如下: public class UserInfo { public int UserId { get; set; } public string UserName { get; set; } } 二.后台代码 后台Action代码如下: [HttpPost] public Ac

用几种不同的方式表示数组

#include<stdio.h> void main() { int a[]={0,1,2,3,4,5}; int i; int *p; p=a; for(i=0;i<6;i++) printf("a[%d]=%d\t",i,a[i]); printf("\n"); for(i=0;i<6;i++) printf("*(p+%d)=%d\t",i,*(p+i)); printf("\n"); for(

(转发)在ASP.NET MVC中以post方式传递数组参数的示例

最近在工作中用到了在ASP.NET MVC中以post方式传递数组参数的情况,记录下来,以供参考. 一.准备参数对象 在本例中,我会传递两个数组参数:一个字符串数组,一个自定义对象数组.这个自定义对象UserInfo定义如下: public class UserInfo { public int UserId { get; set; } public string UserName { get; set; } } 二.后台代码 后台Action代码如下: [HttpPost] public Ac

用最复杂的方式学会数组(Python实现动态数组)

Python序列类型 在本博客中,我们将学习探讨Python的各种"序列"类,内置的三大常用数据结构--列表类(list).元组类(tuple)和字符串类(str). 不知道你发现没有,这些类都有一个很明显的共性,都可以用来保存多个数据元素,最主要的功能是:每个类都支持下标(索引)访问该序列的元素,比如使用语法 Seq[i].其实上面每个类都是使用 数组 这种简单的数据结构表示. 但是熟悉Python的读者可能知道这3种数据结构又有一些不同:比如元组和字符串是不能修改的,列表可以修改.

PHP 更快的方式实现数组去重

概述 使用PHP的array_unique()函数允许你传递一个数组,然后移除重复的值,返回一个拥有唯一值的数组.这个函数大多数情况下都能工作得很好.但是,如果你尝试在一个大的数组里使用array_unique()函数,它会运行地慢一些. 有一个比较好而且更快的函数array_flip()来替代使用array_unique()函数来创建唯一的数组.这个魔法般的函数会交换数组里面每一个元素的键和值,因为键值必须唯一,因此,你会得到跟array_unique()函数一样的结果. 更快的方式实现PHP

线性表的2种实现方式:数组和链表

数组实现方式: 1 #ifndef LINEARLIST_H 2 #define LINEARLIST_H 3 #include<iostream> 4 #include<cstdlib> 5 #include<new> 6 using std::cout; 7 using std::endl; 8 template<class T> 9 class LinearList 10 { 11 public: 12 LinearList(int MaxListSi

Java并发和多线程2:3种方式实现数组求和

本篇演示3个数组求和的例子. 例子1:单线程例子2:多线程,同步求和(如果没有计算完成,会阻塞)例子3:多线程,异步求和(先累加已经完成的计算结果) 例子1-代码 package cn.fansunion.executorservice; public class BasicCaculator { public static long sum(int[] numbers){ long sum = 0; for(int i=0;i<numbers.length;i++){ sum += numbe