2017-02-11 26 views
0

У меня есть объект, содержащий список экземпляров абстрактного класса.Как вы возвращаете объект, который принимает чистые виртуальные (const ref) аргументы в C++?

class A { }; // abstract class 

class B { 
public: 
    void addA(const A& a) { 
    list_of_a_.push_back(&a); 
    } 
private: 
    std::vector<const A*> list_of_a_; 
}; 

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

Тогда у меня есть заводская функция, которая создает B и возвращает ее.

B CreateB() { 
    B b; 
    DerivationOfA a; // subclass of abstract class A. 
    b.addA(a); 
    return b; 
} 

int main() { 
    B b = CreateB(); 
    b.DoSomethingWithA(); // bad memory, 'a' was destroyed. 
} 

Проблема возникает после того, как функция B возвращается из функции «create», экземпляр A уничтожается в стеке. То же самое происходит, когда я использую умный указатель. Я хочу избежать использования обычных указателей, чтобы избежать дополнительных проблем с управлением памятью.

Есть ли уловка для этого, чтобы я мог сохранить функцию CreateB, избегайте использования указателей, но все же сможете передавать абстрактные классы в B? Я в порядке с использованием pass по значению, просто C++ не позволяет делать это с абстрактными классами.

Редактировать: Я знаю, почему это происходит, но я не уверен, как обойти его при использовании чистых виртуальных объектов. Вне использования C-указателей, которых я хочу избежать, я не уверен, как избежать проблемы с определением области охвата.

Edit 2: Чистые виртуальные классы вместо объектов быть более точным.

Edit 3: Аннотация классов вместо чисто виртуальных классов для удовлетворения придирки.

+0

Это не имеет ничего общего с виртуальными функциями или абстрактными классами, но все, что вы храните указатели на локальные переменные. Подумайте об использовании указателей полностью, если у вас есть абстрактные базовые классы, а затем подумайте о владении указателем и о том, как их можно управлять с помощью интеллектуальных указателей. –

+0

нет такого понятия, как «чистый виртуальный объект». Термин «чистый виртуальный класс» можно перевести как «класс, который не может быть инстанцирован», т. Е. Вы не можете создать объект этого класса. – user463035818

+0

Вам нужно «новое» вверх 'DerivationOfA' (или, может быть,' make_unique' или 'make_shared', если вы предпочтете, что внутренне собираются в конечном итоге делать «новые» между прочим). Нет никакого способа обойти это, потому что вы не можете указывать указатели на локальные переменные, и вы не можете делать ничего подобного вектору , поддерживая производные классы. – TheUndeadFish

ответ

4

У вас запутанный дизайн. Плохая идея получает объект по ссылке и сохраняет его указатель.

  • Использование умных указателей.
  • Если вы хотите сохранить объект по указателю, вам нужно выделить его в кучу (оператором new) и освободить (от оператора delete) в вашем контейнере (класс с векторным членом).
  • Если вы хотите передать его по ссылке, вы должны гарантировать, что объект не будет удален. Или вам нужно сделать копию этого объекта во время этого вызова функции.

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

+0

В результате я использовал smart shared_ptr для хранения этих объектов. Таким образом, его можно скопировать и избежать стека. – Sefu

-1

Функция CreateB создает объект b в стеке, и когда мы возвращаемся от этой функции, стек разматывается, а b выходит за рамки.

Вы можете создать объект b в функции main, а затем вызвать функцию addA на этом объекте.

EDIT

Создать b на куче и вернуть ссылку на него.

B* CreateB() { 
    B *b = new B; 
    // rest of the code 
    return b; 
} 
+0

Это то, что я изначально имел, но он становится беспорядочным (выше это очень тривиальный пример всего кода). Я хотел бы иметь фабричную функцию, которая обрабатывает беспорядок и сохраняет main() чистым. – Sefu

+0

Вы можете выделить 'b' в кучу, используя' new'. B * b = новый B; – Rishi

+0

не должен 'return * b' быть' return b'? 'b' - это указатель, поэтому возвращаем его, как возвращаем ссылку на что-то. – Fureeish

0

Определить конструктор для перемещения B.

B factory() { 
    B x; 
    // some code 

    return std::move(x); 
    // return x; if with copy elision 
} 

Должно работать.

Если семантика перемещения не является вариантом.

struct B { 
public: 
    class Holder { 
    public: 
    static Holder NewHolder() { 
     return Holder(new B{}); 
    } 
    B *ptr_; 

    private: 
    Holder(B *ptr) : ptr_(ptr) {} 
    }; 

    B() : resource_(new int) { 
    std::cout << "Construct" << std::endl; 
    } 

    B(Holder x) { 
    resource_ = x.ptr_->resource_; 
    x.ptr_->resource_ = nullptr; 
    delete x.ptr_; 
    } 

    ~B() { 
    std::cout << resource_ << " deleted by " << this << std::endl; 
    delete resource_; 
    } 

    int *resource_; 
}; 

B::Holder factory() { 
    B::Holder holder = B::Holder::NewHolder(); 
    B &b = *holder.ptr_; 
    // some code 

    return holder; 
} 

int main() { 
    B b = factory(); 

    return 0; 
} 

Держатель - это вложенный класс, который содержит B, выделенный новым. И этот B не будет уничтожен до тех пор, пока конструктор B(Holder x) не украсть resouce от держателя, тогда конструктор удалит пустой B.