2016-03-29 2 views
1

Я использую производный класс и вектор указателей на объекты из указанного класса. У меня возникли проблемы с выполнением конструкторов копирования и т. Д., Даже после многого прочтения. Я использую C++ 11.Вектор указателей объектов и объектов, выходящих из области видимости

У меня есть петля, где создаются новые объекты производного класса, и тогда я хочу поместить их в вектор. Но поскольку это вектор указателей, я должен вместо этого указать адрес созданного объекта.

Я считаю, что мои проблемы проистекают из этого факта вместе с объектами, выходящими из области видимости и т. Д., И я не могу реализовать конструкторы копирования/конструкторы назначения копирования и т. Д. Удовлетворительным образом.

Я сделал минимальный пример, который, на мой взгляд, иллюстрирует мою проблему. Скажем, начальная настройка выглядит следующим образом (я знаю, что это не имеет особого смысла, например, с указателем *n, но я думаю, что это показывает проблемы с моим фактическим кодом):

using namespace std; 
#include <iostream> 
#include <vector> 

class Base { 

    protected: 
    int* n; 

    public: 
    void display(string name="") {cout << name << "n = " << *n << endl;} 

    Base() {} 
    Base(int n_) { 
     *n=n_; 
     cout << "Initialised to " << *n << endl; 
    } 
}; 

class Derived : public Base { 

    public: 

    Derived() : Base() {} 
    Derived(int n_) : Base(n_) {} 
}; 

int main(int argc, const char* argv[]) { 

    vector<Base*> list; 
    for(int i=0;i<5;i++) { 
     Derived tmp(i); 
     tmp.display("Tmp: "); 
     list.push_back(&tmp); 
    } 

    cout << endl; 

    for(int i=0;i<list.size();i++) { 
     cout << "Address of " << i << ": " << list[i] << endl; 
     list[i]->display("Element "+to_string(i)+" : "); 
    } 

} 

Выход этого является :

Initialised to 0 
Tmp: n = 0 
Initialised to 1 
Tmp: n = 1 
Initialised to 2 
Tmp: n = 2 
Initialised to 3 
Tmp: n = 3 
Initialised to 4 
Tmp: n = 4 

Address of 0: 0x7fff3a1df2d0 
Element 0 : n = 0 
Address of 1: 0x7fff3a1df2d0 
Element 1 : n = 0 
Address of 2: 0x7fff3a1df2d0 
Element 2 : n = 0 
Address of 3: 0x7fff3a1df2d0 
Element 3 : n = 0 
Address of 4: 0x7fff3a1df2d0 
Element 4 : n = 0 

Таким образом, после цикла все элементы в списке точки к тому же адресу, где п = 0 (вероятно, неопределенное поведение от tmp из области видимости).

Так, а не просто положить адрес tmp в list Я думаю, что я каким-то образом придется создавать экземпляры Derived, которые выживают петлю, пока еще только имея адреса в list.

Как уже упоминалось, я пытался сделать это, используя различные специальные функции-члены без везения.

Обратите внимание, что может показаться, что реализация вектора самих объектов может быть проще, но это приводит к другим проблемам в моем фактическом коде. Если я не смогу получить эту работу, я попробую это вместо этого.

+3

Выделение неинициализированного указателя является неопределенным поведением: вам нужно выделить некоторую память для 'n', чтобы указать, прежде чем вы будете делать * * n = n_' в конструкторе' B'. Для 'list.push_back (&tmp);' вы на самом деле выделили (автоматическую) память, но, как вы можете видеть по значению указателей, память всегда одна и та же: она используется повторно, поскольку область действия 'tmp' переходит только в конец цикла. – BeyelerStudios

+0

Какова цель 'Base :: n'? Почему это указатель? –

+0

@BeyelerStudios: Вы имеете в виду, например, добавление' n = 0' перед '* n = n_'? @Joachim Pileborg: Как упоминалось в OP, я знаю, что это не имеет смысла, это очень простая примерная версия большой программы – jorgen

ответ

4

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

Вместо этого вам необходимо динамически распределить эти объекты, используя, например, new. Вы можете обернуть эти указатели, используя C++11 smart pointers.

+0

Вместе с комментарием @BeyelerStudios это сработало. – jorgen

+1

Вероятно, вы получаете segfault из-за строки '* n = n_', а не' new' –

2

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

Вместо этого вам придется создавать новые объекты.

int main(int argc, const char* argv[]) { 
    vector<Base*> list; 
    for(int i=0;i<5;i++) { 
     auto tmp = new Derived{i}; 
     tmp->display("Tmp: "); 
     list.push_back(tmp); 
    } 
    // ... 
    } 
} 

Теперь вам нужно убедиться, что вы освобождаете объекты по мере необходимости. Всякий раз, когда это возможно предпочитают прибегать к unique_ptr<> или shared_ptr<>:

int main(int argc, const char* argv[]) { 
    vector<unique_ptr<Base>> list; 
    for(int i=0;i<5;i++) { 
     auto tmp = make_unique<Derived>(i); 
     tmp->display("Tmp: "); 
     // this should move the unique_ptr and therefore transfer 
     // its ownership to the vector. 
     list.emplace_back(tmp); 
    } 
    // ... 
    } 
} 

Теперь объекты будут уничтожены, либо когда они удаляются из вектора, или когда вектор уничтожается. С shared_ptr, который может быть отложен, пока какая-либо часть вашей программы не будет содержать shared_ptr<> для одного и того же объекта.

Смежные вопросы