C ++ заполняющие векторы внутри конструкторов классов - PullRequest
2 голосов
/ 30 сентября 2011

У меня есть проблемные элементы адресации внутри объектов Class3, пожалуйста, посмотрите на эти упрощенные классы:

class Class1 {
public:
    std::vector<Class2> c2v;
    Class1();
};

class Class2 {
    Class1 *instance;
    int id;
public:
    std::vector<Class3> c3v;
    Class2 ( Class1 *instance, int id );
};

class Class3 {
    Class1 *instance;
    int id;
public:
    Class3 ( Class1 *instance, int id );
};

И их конструкторы:

Class1::Class1()
{
    for ( int i = 0; i < noi; ++i ) {
        c2v.push_back ( Class2 ( this, i ) );
    }
}

Class2::Class2 ( Class1 *instance, int id )
{
    this->instance = instance;
    this->id = id;

    for ( int k = 0; k < nok; ++k ) {
        c3v.push_back ( Class3 ( this->instance, k ) );
    }
}

В main () объект Class1 создается с помощью конструктора по умолчанию. Поэтому он создает вектор c2v и заполняет его объектами noi класса 2.

В то же время, когда объекты Class2 помещаются в вектор c2v, они создаются, и каждый из них создает вектор c3v и заполняет его «не» объектами Class3.

Код компилируется нормально, но во время выполнения при доступе к публичным атрибутам Class2 из объектов Class3 (через this->instance->c2v[0].getSomeAttribute()) программа останавливается с EXC_BAD_ACCESS.

Проверка с помощью отладчика показывает, что указатель на c2v[0] поврежден (его значение становится 0x0).

Я новичок в C ++. Мне было интересно, что это за ошибка при попытке создания экземпляров векторов таким образом. Должен ли я только объявлять векторы и заполнять их в отдельной функции, вызываемой после завершения создания всех экземпляров Class2 и Class3?

Я добавляю некоторый фактический код, надеюсь, что его чтение не займет много времени (пожалуйста, поймите, я пропустил некоторые предварительные объявления и директивы препроцессора):

// global variables
extern char *filename; //not used
extern int nodes;
extern int numProdotti;
extern int classe; //not used
extern int maxNumRange; //not used
extern int *numRanges;
extern int *list ;
extern int *typeProdMarket;
extern int totalQtyDemand; //not used
extern int totNumRanges; //not used

extern struct product {
    int     qty;
    int     cost;
} **prodMarket;

extern struct range {
    int     discount;
    int     startOfRange;
} **rangeMarket; //not used

int main ( int argc, char *argv[] )
{
    Ctqd instance;
    instance.runOpt();
}

class Ctqd {
    void greedySimple();
    void greedySimpleReverse();
    void greedyFromY();
    void greedyByNiceness1();
    void greedyByNiceness2();
    void greedyByStepTier1();
    void greedyByStepTier2();
    void randomLocalSearch1();
    void LocalSearch2opt();
public:
    std::vector<Item> items;
    std::vector<Supplier> suppliers;
    Solution *solution;
    Ctqd();
    ~Ctqd();
    void runOpt();
};

class Supplier {
    Ctqd *instance;
    int id;
    int refTotQty;
    int refDiscount;
    int refDiscountTier;
    double refTotPrice;
    double refUnitPrice;
    double niceness;
    int purTotQty;
    int purDiscount;
    int purDiscountTier;
public:
    std::vector<Offer> offers;
    Supplier ( Ctqd *instance, int id );
    int getId();
    int getRefTotQty();
    int getRefDiscount();
    int getRefDiscountTier();
    double getRefTotPrice();
    double getRefUnitPrice();
    double getNiceness();
    int getPurTotQty();
    int getPurDiscount();
    int getPurDiscountTier();
    void updateStats();
};

class Offer {
    Supplier *supplier;
    int id;
    int refQty;
    double refPrice;
    double niceness;
    int purQty;
public:
    Offer ( Supplier *supplier, int id );
    int getId();
    int getRefQty();
    double getRefPrice();
    double getNiceness();
    int getPurQty();
    void setPurQty ( int qty );
    int remainingQty();
};

Ctqd::Ctqd()
{
    // constructing items vector
    for ( int k = 0; k < numProdotti; ++k ) {
        items.push_back ( Item ( this, k ) );
    }

    // constructing suppliers vector
    for ( int i = 0; i < nodes; ++i ) {
        suppliers.push_back ( Supplier ( this, i ) );
    }

    // building solution
    solution = new Solution ( this );
}

Supplier::Supplier ( Ctqd *instance, int id )
{
    this->instance = instance;
    this->id = id;
    // computing total reference quantity
    refTotQty = 0;

    for ( int k = 0; k < numProdotti ; ++k ) {
        refTotQty += std::min ( list[ k ] , prodMarket[ this->id ][ k ].qty );
    }

    // computing reference discount coefficients
    refDiscount = 0;
    refDiscountTier = 0;

    for ( int r = 0; r < numRanges[ this->id ]; ++r ) {
        if ( refTotQty < rangeMarket[ this->id ][ r ].startOfRange ) {
            break;
        }
        else {
            refDiscount = rangeMarket[ this->id ][ r ].discount;
            refDiscountTier = r;
        }
    }

    //computing total reference price
    refTotPrice = 0;

    for ( int k = 0; k < numProdotti ; ++k ) {
        refTotPrice += prodMarket[ this->id ][ k ].cost * std::min ( list[ k ] , prodMarket[ this->id ][ k ].qty );
    }

    refTotPrice = refTotPrice * ( 1.000 - refDiscount / 100.000 );
    //computing reference unit price
    refUnitPrice = refTotPrice / refTotQty;
    //computing supplier niceness
    niceness = refTotQty / refUnitPrice;
    purTotQty = 0;
    purDiscount = 0;
    purDiscountTier = 0;

    // building offers vector
    for ( int k = 0; k < numProdotti; ++k ) {
        offers.push_back ( Offer ( this, k ) );
    }
}

Offer::Offer ( Supplier *supplier, int id )
{
    this->supplier = supplier;
    this->id = id;
    // computing reference quantity
    refQty = std::min ( list[ this->id ] , prodMarket[ this->supplier->getId() ][ this->id ].qty );
    // computing reference price
    refPrice = prodMarket[ this->supplier->getId() ][ this->id ].cost * ( 1.000 - this->supplier->getRefDiscount() / 100.000 );
    // computing niceness of the offer
    niceness = refQty / ( ( prodMarket[ this->supplier->getId() ][ this->id ].cost + refPrice ) / 2 );
    // init purQty to 0
    purQty = 0;
}

Вот где я получаю EXC_BAD_ACCESS:

int Offer::remainingQty()
{
    return prodMarket[ supplier->getId() ][ id ].qty - purQty;
}

Я провел несколько экспериментов: Изменены векторы в классе Ctqd и в классе Supplier на векторы указателей на объекты. Проблема была решена только частично. Все еще имел EXC_BAD_ACCESS при создании объектов предложений.

Для конструктора класса Offer нужны данные из объекта Supplier, который его создал. Я думал, что доступ к этим данным, когда вектор поставщиков еще заполнен, может привести к проблемам, поэтому я создал небольшую функцию initialize () в поставщике:

void Supplier::initialize()
{
    // constructing offers vector
    for ( int k = 0; k < numProdotti; ++k ) {
        offers.push_back ( new Offer ( this->instance, id, k ) );
    }
}

И добавил это в конце конструктора класса Ctqd:

// init newly built objects
for ( int i = 0; i < nodes; ++i ) {
    suppliers[i]->initialize();
}

Теперь все работает нормально. Но я так и не понял, в чем конкретно проблема.

Ответы [ 5 ]

1 голос
/ 30 сентября 2011

Самым простым (и лучшим) решением этой проблемы является использование std::deque вместо std::vector. Вам не нужно использовать указатели; Вы можете придерживаться своего первоначального плана подталкивания объектов.

При использовании deque, push_back гарантированно не делает недействительными ссылки на его элементы. То же самое для push_front, на самом деле. И он по-прежнему поддерживает постоянный произвольный доступ (foo[n]).

Обычно, когда вы создаете контейнер, полный объектов, вам нужна дека.

Как правило, std::deque, вероятно, лучшая структура данных, которую вы никогда не использовали.

0 голосов
/ 30 сентября 2011

В то время как перемещение содержимого вектора и копирование объектов Class2 или Class3 не должно создавать проблем (в отличие от того, что могут указывать другие ответы), проблема возникает при создании локального объекта Class1 и скопируйте это вокруг. Когда вы сейчас пытаетесь получить доступ к исходному объекту Class1 с помощью одного из указателей instance, этот объект, возможно, уже был уничтожен, и поэтому доступ к одному из его членов вызывает ошибку сегмента.

0 голосов
/ 30 сентября 2011

Изменение

std::vector<Class2> c2v;

до

std::vector<Class2 *> c2v;

и

std::vector<Class3> c3v;

до

std::vector<Class3*> c3v;

Проблема в том, что вы берете адреса локальных объектов внутри вектора. Когда вектору требуется больше места, он перераспределяет свою память и, следовательно, объекты Class2 и Class3 меняют адрес.

class Class1 {
public:
    std::vector<Class2 *> c2v;
    Class1();
};

class Class2 {
    Class1 *instance;
    int id;
public:
    std::vector<Class3 *> c3v;
    Class2 ( Class1 *instance, int id );
};

class Class3 {
    Class1 *instance;
    int id;
public:
    Class3 ( Class1 *instance, int id );
};


Class1::Class1()
{
    for ( int i = 0; i < noi; ++i ) {
        c2v.push_back ( new Class2 ( this, i ) );
    }
}

Class2::Class2 ( Class1 *instance, int id )
{
    this->instance = instance;
    this->id = id;

    for ( int k = 0; k < nok; ++k ) {
        c3v.push_back ( new Class3 ( this->instance, k ) );
    }
}

И не забудьте очистить деструкторы Class1 'и Class2.

EDIT: Почему произошло такое странное поведение:

Шаг 1

Ctqd::Ctqd()
{
    for ( int i = 0; i < nodes; ++i ) {
        suppliers.push_back ( Supplier ( this, i ) ); // <-- Supplier's constructor is invoked here
                                                // to construct automatic object of Supplier class
    }
}

Шаг 2. Мы внутри конструктора автоматического объекта:

Supplier::Supplier ( Ctqd *instance, int id )
{
    for ( int k = 0; k < numProdotti; ++k ) {
        offers.push_back ( Offer ( this, k ) ); // consider that we are passing this to 
                                               // Offer's constructor. this is a pointer to
                                              // automatic variable
    }
}

Шаг 3. Вернуться к шагу 1. Но теперь у нас есть автоматический объект, созданный Поставщиком

Ctqd::Ctqd()
{
    for ( int i = 0; i < nodes; ++i ) {
        suppliers.push_back ( Supplier ( this, i ) ); // <-- automatic object of class Supplier
                                                     // is created. Now as long as push_back() taking
                                                    // parameter by value yo are passing copy of
                                                   // automatic object to push_back();
                                                  // as long as you didn't define copy constructor
                                                 // for Supplier compiler adds default for you.
    }
}

Шаг 4. Копия автоматического объекта сохраняется в suppliers вектор. Новый объект (копия автоматического объекта) имеет вектор offers с теми же значениями, что и у нашего автоматического объекта (спасибо конструктору копирования вектора). Таким образом, у каждого объекта есть член supplier, который указывает на ... все они указывают на автоматический объект.

Ctqd::Ctqd()
{
    for ( int i = 0; i < nodes; ++i ) {
        suppliers.push_back ( Supplier ( this, i ) ); // <-- magic here
    }
}

Шаг 5. Автоматический объект был разрушен (помните, как вы передали указатель на этот объект конструктору Оффера?). Упс, вы говорите.

Ctqd::Ctqd()
{
    for ( int i = 0; i < nodes; ++i ) {
        suppliers.push_back ( Supplier ( this, i ) ); 
                                                      // <-- automatic doesn't exist no more
    }
}

Так что же произойдет, если один из методов поставщика попытается получить доступ к объекту Supplier через член supplier (что, мы, умные дети, знаем, указывает на мертвый объект ... так грустно ... QQ). Угадай. Я верю, что он умрет, осознав, что впервые в жизни видит мертвый предмет;).

0 голосов
/ 30 сентября 2011

Для начала ваш код не будет компилироваться: когда компилятор встречает объявление Class1::c2v, Class2 является неизвестным символом.Если я добавлю предварительные объявления (и определение noi), он по-прежнему содержит неопределенное поведение и не компилируется с g ++, по крайней мере, с обычными параметрами.Не допускается создание экземпляра стандартного шаблона с неполным типом.Поскольку ваши типы имеют циклическую зависимость, вам придется где-то использовать указатели.

Кроме того, что происходит с указателями в Class2 и Class3 при копировании объектов (и std::vectorбудет копировать).Из сделанного вами комментария (Class2 представляет поставщиков, а Class3 представляет предложения), эти классы, вероятно, должны быть не копируемыми.Это во многом зависит от дизайна, но в большинстве случаев классы сущностей, которые моделируют проблемную область, не должны копироваться.Это также приводит к использованию указателей на них, а не копий.(Ссылочная семантика, а не семантика значений.)

Конечно, использование указателей приводит к проблемам времени жизни объекта.Вам нужно будет решить, когда и как появятся поставщики и предложения, а когда они прекратят свое существование.Это должно быть частью вашего дизайна.

0 голосов
/ 30 сентября 2011

Вы не подчиняетесь правилу 3. Вы должны реализовать деструктор, конструктор копирования и оператор присваивания для всех ваших классов.

Рассмотрим строку:

c2v.push_back ( Class2 ( this, i ) );

Это создаст временный объект, сделает его копию и поместит его в вектор. Копия и временный объект будут указывать через элемент экземпляра в одно и то же место. Однако, когда временный объект уничтожается (непосредственно перед следующей строкой), эта память освобождается и становится доступной для использования. Так что теперь у вас внутри вашего объекта будет объект типа Class2 с недопустимым полем.

Это только мое предположение. Пожалуйста, отправьте код в main, если это не поможет.

...