为什么我在for循环中无法访问动态分配的内存?

5

我为我的子类类型股票(stock)新建了一个继承自基类工具(instrument)的内存,但当我尝试访问数组的第二个元素时,它会抛出错误。当我的新数组大小为1时,情况是好的。

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

class Instrument{

public:
    virtual void display(){}
    virtual void output(){}
    virtual void readFile(){}
    virtual ~Instrument(){}
};
class Stock : 
    public Instrument{
public:
    Stock(){

    }
    virtual void input(){
        cout << "This is stock, please input its information: ";
        cin >> name >> bidPrice >> askPrice >> lastPrice >> issueExchange;
    }
    virtual void display(){
        cout <<"This is to display stock: "<< name << " "
            << bidPrice << " "
            << askPrice << " "
            << lastPrice << " "
            << issueExchange << " "
            << endl;
    }
    virtual void output(){
        ofstream myfile;
        myfile.open("Stock.txt", ios::out | ios::app);
        if (myfile.is_open()){
            myfile << "This is a stock: "
                << name << " "
                << bidPrice << " "
                << askPrice << " "
                << lastPrice << " "
                << issueExchange << " "
                << endl;
        }
        else cout << "Unable to open file";
    }
    virtual void readFile(){
        string line;
        ifstream myfile("Stock.txt");
        cout << "\nThis is file stored\n";
        if (myfile.is_open())
        {
            while (getline(myfile, line))
            {
                cout << line << '\n';
            }
            myfile.close();
        }
    }
    virtual ~Stock(){}
private:
    char name[13];
    double bidPrice;
    double askPrice;
    double lastPrice;
    int issueExchange;

};


int main(){

    const int N = 5;//it works fine if I use N=1;
    Instrument *pBase = NULL;
    pBase = new Stock[N];

    for (int i = 0; i < N; i++){
        pBase[i].input();// here throws an exception and ends the program
        pBase[i].display();
        pBase[i].output();
    }
    pBase[N - 1].readFile();
    delete[] pBase;

    system("pause");
    return 0;

} 

@GermaineJason 这不是Java或C#。有对象,而空指针异常不存在。 - deviantfan
@MooingDuck,它已经从字符串更改为char [13],你看到我的#include<string>了吗? - user4592991
1
问题在于您正在将 Stock 数组访问,就好像它是一个 Instrument 数组。多态不能在整个数组之间起作用,您需要指向每个单独元素的指针。 - Galik
1
@Galik:请不要在评论区回答问题。那是用来回答的_answer_部分。谢谢。 - Lightness Races in Orbit
@deviantfan:是的,他错了。 - Lightness Races in Orbit
显示剩余2条评论
3个回答

7

多态和指针算术不搭配,因为对象在数组中的排列取决于最终派生类的大小,而多态会丢失这些信息。动态分配只是一个误导,你可以在以下情况下看到相同的问题:

Derived array[2];
Base* p = array;

printf("%p\n", &array[0]);
printf("%p\n", p);
printf("%p\n", &array[1]);
printf("%p\n", p + 1);

printf("%z\n", sizeof (array[0]));
printf("%z\n", sizeof (*p));

注意,使用 array 的指针值向前移动 sizeof (Derived),但是使用 p 的指针算术运算向前移动 sizeof (Base),并且无法找到真正的对象。
通常您会使用 Base* 数组来修复这个问题,而不是单个的 Base* 与指针算术相结合。
Base* pp[2];
for( auto& elem : array ) pp[&elem - array] = &elem;

printf("%p\n", &array[1]);
printf("%p\n", pp[1]);

// use (*pp[1]) or pp[1]->whatever

另一种选项是使用记住原始类型的对象:

Derived* allocated = new Derived[N];
std::function<Base& (int)> poly = [allocated](int i){ return allocated[i]; };

使用poly(i)代替p[i]

但是要注意,不能执行delete [] &poly(0);,因为delete[]也不具有多态性。

使用std::unique_ptr<Derived[]>std::bind,可以在访问器对象最终超出范围时自动释放内存。


3
虽然准确,但这个答案在解释方面有些不足,不是吗? - Mooing Duck
是的,这是关键,但我的任务要求我同时使用多态和数组,我尝试使用instrument *p[N],并访问p[i],但在这里它无法工作。这是我尝试的新方法。 - user4592991
1
@Leewis 你可以使用指向单个对象的指针数组(任何类型)来代替对象本身的数组。 - deviantfan
是的,我在 Stack Overflow 上看到初学者代码中经常出现这种模式。但我不喜欢它。nnew,而且没有内存局部性 :( - Lightness Races in Orbit
2
@LightnessRacesinOrbit:你还可以使用指针数组,每个指针对应数组中的相应元素。 - Ben Voigt
显示剩余2条评论

0

虽然Ben先生的方法绝对是正确的,但我完全感觉他的C++和我的C++不是同一种语言,他在这里混合了一些奇怪的东西,因此根据他的想法,我尝试修改我的代码如下。

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <memory>
using namespace std;

class Instrument{

public:
    virtual void display() = 0;
    virtual void output() = 0;
    virtual void readFile() = 0;
    virtual ~Instrument(){};
};
class Stock :
    public Instrument{
public:
    Stock(){
        cout << "This is stock, please input its information: ";
        cin >> name >> bidPrice >> askPrice >> lastPrice >> issueExchange;
    }
    virtual void display(){
        cout << "This is to display stock: " << name << " "
            << bidPrice << " "
            << askPrice << " "
            << lastPrice << " "
            << issueExchange << " "
            << endl;
    }
    virtual void output(){
        ofstream myfile;
        myfile.open("Stock.txt", ios::out | ios::app);
        if (myfile.is_open()){
            myfile << "This is a stock: "
                << name << " "
                << bidPrice << " "
                << askPrice << " "
                << lastPrice << " "
                << issueExchange << " "
                << endl;
        }
        else cout << "Unable to open file";
    }
    virtual void readFile(){
        string line;
        ifstream myfile("Stock.txt");
        cout << "\nThis is file stored\n";
        if (myfile.is_open())
        {
            while (getline(myfile, line))
            {
                cout << line << '\n';
            }
            myfile.close();
        }
    }
    virtual ~Stock(){}
private:
    string name;
    double bidPrice;
    double askPrice;
    double lastPrice;
    int issueExchange;

};
class Option :
    public Instrument{
public:
    Option(){
        cout << "This is option, please input its information: ";
        cin >> name >> uname >> bidPrice >> askPrice >> lastPrice >> contractSize >> exp;
    }
    virtual void display(){
        cout << "This is to display option: "
            << name << " "
            << uname << " "
            << bidPrice << " "
            << askPrice << " "
            << lastPrice << " "
            << contractSize << " "
            << exp << " "
            << endl;
    }
    virtual void output(){
        ofstream myfile;
        myfile.open("Option.txt", ios::out | ios::app);
        if (myfile.is_open()){
            myfile << "This is an option: "
                << name << " "
                << uname << " "
                << bidPrice << " "
                << askPrice << " "
                << lastPrice << " "
                << contractSize << " "
                << exp << " "
                << endl;
        }
        else cout << "Unable to open file";
    }
    virtual void readFile(){
        string line;
        ifstream myfile("Option.txt");
        cout << "\nThis is file stored\n";
        if (myfile.is_open())
        {
            while (getline(myfile, line))
            {
                cout << line << '\n';
            }
            myfile.close();
        }
    }
    virtual ~Option(){}
private:
    string name;
    string uname;
    double bidPrice;
    double askPrice;
    double lastPrice;
    int contractSize;
    double exp;
};
class Future :
    public Instrument{
public:
    Future(){
        cout << "This is option, please input its information: ";
        cin >> name >> uname >> bidPrice >> askPrice >> lastPrice >> contractSize >> tickSize >> contractMonth;
    }
    virtual void display(){
        cout << "This is to display option: "
            << name << " "
            << uname << " "
            << bidPrice << " "
            << askPrice << " "
            << lastPrice << " "
            << contractSize << " "
            << tickSize << " "
            << contractMonth << " "
            << endl;
    }
    virtual void output(){
        ofstream myfile;
        myfile.open("Future.txt", ios::out | ios::app);
        if (myfile.is_open()){
            myfile << "This is a future: "
                << name << " "
                << uname << " "
                << bidPrice << " "
                << askPrice << " "
                << lastPrice << " "
                << contractSize << " "
                << tickSize << " "
                << contractMonth << " "
                << endl;
        }
        else cout << "Unable to open file";
    }
    virtual void readFile(){
        string line;
        ifstream myfile("Future.txt");
        cout << "\nThis is file stored\n";
        if (myfile.is_open())
        {
            while (getline(myfile, line))
            {
                cout << line << '\n';
            }
            myfile.close();
        }
    }
    virtual ~Future(){}
private:
    string name;
    string uname;
    double bidPrice;
    double askPrice;
    double lastPrice;
    int contractSize;
    int tickSize;
    int contractMonth;
};

int main(){

    int N = 20;
    //shared_ptr<Instrument> pBase[N];
    vector<shared_ptr<Instrument>> pBase(N);
    int i = 5;
    for (i = 0; i < N; i++) pBase[i] = make_shared<Stock>();
    for (i = 0; i < N; i++){
        pBase[i]->display();
        pBase[i]->output();
    }
    pBase[N - 1]->readFile();

    for (i = 0; i < N; i++) pBase[i] = make_shared<Option>();
    for (i = 0; i < N; i++){
        pBase[i]->display();
        pBase[i]->output();
    }
    pBase[N - 1]->readFile();

    for (i = 0; i < N; i++) pBase[i] = make_shared<Future>();
    for (i = 0; i < N; i++){
        pBase[i]->display();
        pBase[i]->output();
    }
    pBase[N - 1]->readFile();

    system("pause");
    return 0;

}

0

简短回答:不要将子类数组(Stock[N])转换为基类指针(pBase)。直接使用Stock*,或者创建一个指针数组。

auto arr = new Stock*[N];
for (int i = 0; i < N; i++) {
  arr[i] = new Stock();
}

// unrelated but suggested: C++11 unique_ptr is recommended:
vector<unique_ptr<Stock>> v(N);

详细原因:
1)数组方括号运算符是语法糖:a[b] -> *(a + b)
2)指针算术不具有多态性,它始终基于静态类型:
pBase[i] -> *(pBase+i) -> *(pBase*)((char*)pBase + sizeof(Instrument) * i);

3) 然而,这正是你想要的:

pBase[i] -> *(pBase+i) -> *(pBase*)((char*)pBase + sizeof(Stock) * i);

4) 只要 sizeof(Instrument) != sizeof(Stock),你就会遇到麻烦。


我做了,看我的答案,vector<shared_ptr<Instrument>> pBase(N); - user4592991

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接