C++ new[] в указателе базового класса, сбой при доступе к массиву

StackOverflow https://stackoverflow.com/questions/829700

Вопрос

Когда я выделяю один объект, этот код работает нормально.Когда я пытаюсь добавить синтаксис массива, происходит сбой.Почему это?Моя цель здесь — скрыть от внешнего мира тот факт, что класс c использует внутри себя объекты b.Я выложил программу кодовая панель для тебя, чтобы играть.

#include <iostream>

using namespace std;

// file 1

class a
{
    public:
        virtual void m() { }
        virtual ~a() { }
};

// file 2

class b : public a
{
    int x;

    public:
        void m() { cout << "b!\n"; }
};

// file 3

class c : public a
{
    a *s;

    public:
        // PROBLEMATIC SECTION
        c() { s = new b[10]; } // s = new b;
        void m() { for(int i = 0; i < 10; i++) s[i].m(); } // s->m();
        ~c() { delete[] s; } // delete s;
        // END PROBLEMATIC SECTION
};

// file 4

int main(void)
{
    c o;

    o.m();

    return 0;
}
Это было полезно?

Решение

Одна проблема заключается в том, что выражение s [i] использует арифметику указателей для вычисления адреса нужного объекта. Поскольку s определяется как указатель на a , результат является правильным для массива из a s и неверным для массива из b s. Динамическое связывание, обеспечиваемое наследованием, работает только для методов, и ничего больше (например, нет виртуальных элементов данных, нет виртуального sizeof ). Таким образом, при вызове метода s [i] .m () указатель this устанавливается на то, что будет i th a объект в массиве. Но так как на самом деле массив является одним из b , он заканчивается (иногда) указанием где-то в середине объекта, и вы получаете ошибку (возможно, когда программа пытается получить доступ к vtable объекта) ). Возможно, вам удастся устранить проблему путем виртуализации и перегрузки оператора operator [] () . (Однако я не продумал, чтобы увидеть, сработает ли это на самом деле.)

Другая проблема - это delete в деструкторе по тем же причинам. Вы также можете виртуализировать и перегружать его. (Опять же, случайная идея, которая пришла мне в голову. Может не сработать.)

Конечно, приведение (как предложено другими) тоже будет работать.

Другие советы

Создание массива из 10 bс new а затем присваивая его адрес a* просто напрашивается на неприятности.

Не обрабатывайте массивы полиморфно.

Для получения дополнительной информации см. ARR39-CPP.Не обрабатывайте массивы полиморфно, в разделе 06.Массивы и STL (ARR) принадлежащий CERT C++ Стандарт безопасного кодирования.

У вас есть массив типа " b " не типа "а" и вы назначаете его указателю типа a. Полиморфизм не переносится в динамические массивы.

 a* s

к

 b* s

и вы увидите, что это начнет работать.

Только еще не привязанные указатели могут быть обработаны полиморфно. Подумай об этом

 a* s = new B(); // works
 //a* is a holder for an address

 a* s = new B[10]
 //a* is a holder for an address
 //at that address are a contiguos block of 10 B objects like so
 // [B0][B2]...[B10] (memory layout)

когда вы перебираете массив с помощью s, подумайте о том, что используется

 s[i]
 //s[i] uses the ith B object from memory. Its of type B. It has no polymorphism. 
 // Thats why you use the . notation to call m() not the -> notation

до того, как вы преобразовали в массив, который вы только что имели

 a* s = new B();
 s->m();

s здесь просто адрес, а не статический объект, как s [i]. Просто адрес s все еще может быть динамически связан. Что у с? Кто знает? Что-то по адресу s.

См. отличный ответ Ari ниже для получения дополнительной информации о том, почему это также не имеет смысла с точки зрения расположения массивов в стиле C.

Каждый экземпляр B содержит как элемент данных X, так и " vptr " (указатель на виртуальную таблицу).

Каждый экземпляр A содержит только «vptr»

Таким образом, sizeof (a)! = sizeof (b).

Теперь, когда вы сделаете это: " S = new b [10] " вы кладете в память 10 экземпляров b в необработанном виде, а S (который имеет тип a *) получает начало, что и необработанные данные.

в методе C :: m () вы указываете компилятору перебирать массив " a " (потому что s имеет тип a *), НО , s фактически указывает на массив " b " ;. Поэтому, когда вы вызываете s [i], то, что фактически делает компилятор, это "s + i * sizeof (a)" компилятор скачет в единицах «а» вместо единиц "b" и так как a и b не имеют одинаковый размер, вы получите много mambojumbo.

Я нашел обходной путь на основе ваших ответов. Это позволяет мне скрыть особенности реализации, используя слой косвенности. Это также позволяет мне смешивать и сопоставлять объекты в моем массиве. Спасибо!

#include <iostream>

using namespace std;

// file 1

class a
{
    public:
        virtual void m() { }
        virtual ~a() { }
};

// file 2

class b : public a
{
    int x;

    public:
        void m() { cout << "b!\n"; }
};

// file 3

class c : public a
{
    a **s;

    public:
        // PROBLEMATIC SECTION
        c() { s = new a* [10]; for(int i = 0; i < 10; i++) s[i] = new b(); }
        void m() { for(int i = 0; i < 10; i++) s[i]->m(); }
        ~c() { for(int i = 0; i < 10; i++) delete s[i]; delete[] s; }
        // END PROBLEMATIC SECTION
};

// file 4

int main(void)
{
    c o;

    o.m();

    return 0;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top