C ++ new [] в падении указателя базового класса при доступе к массиву - PullRequest
3 голосов
/ 06 мая 2009

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

#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;
}

Ответы [ 5 ]

9 голосов
/ 06 мая 2009

Создание массива из 10 b с new, а затем присвоение его адреса a* просто вызывает проблемы.

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

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

7 голосов
/ 06 мая 2009

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

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

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

4 голосов
/ 06 мая 2009

У вас есть массив типа "b", а не типа "a", и вы назначаете его указателю типа 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 все еще может быть динамически связан. Что у с? Кто знает? Что-то по адресу с.

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

1 голос
/ 06 мая 2009

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

#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;
}
1 голос
/ 06 мая 2009

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...