У меня есть простой контейнерный класс, который указывает на абстрактный класс, и у меня есть функции для получения / установки указателя в контейнерном классе. Конкретнее, класс выглядит так:
class Container
{
Abstract* thing;
public:
void set(Abstract &obj)
{
thing = &obj; //danger of dangling pointer
}
Abstract* get()
{
return thing;
}
};
Abstract
это абстрактный класс. Как уже видно, существует опасность висящего указателя. Я знаю, что могу сделать копию объекта (нового) и затем указать на него. Но я не могу создать экземпляр абстрактного класса. Какие решения для этого есть?
Следующее — просто дополнительная информация:
Определения классов
class Abstract
{
public:
virtual void something() = 0;
};
class Base : public Abstract
{
int a;
public:
Base() {}
Base(int a) : a(a){}
virtual void something()
{
cout << "Base" << endl;
}
};
class Derived : public Base
{
int b;
public:
Derived() {}
Derived(int a, int b) : Base(a), b(b){}
virtual void something()
{
cout << "Derived" << endl;
}
};
Простые тесты
void setBase(Container &toSet)
{
Base base(15);
toSet.set(base);
}
void setDerived(Container &toSet)
{
Derived derived(10, 30);
toSet.set(derived);
}
int main()
{
Container co;
Base base(15);
Derived derived(10, 30);
Base *basePtr;
Derived *derivedPtr;
//This is fine
co.set(base);
basePtr = static_cast<Base *>(co.get());
basePtr->something();
//This is fine
co.set(derived);
derivedPtr = static_cast<Derived *>(co.get());
derivedPtr->something();
//Reset
basePtr = nullptr;
derivedPtr = nullptr;
//Dangling pointer!
setBase(co);
basePtr = static_cast<Base *>(co.get());
basePtr->something();
//Dangling pointer!
setDerived(co);
derivedPtr = static_cast<Derived *>(co.get());
derivedPtr->something();
return 0;
}
То, что вам нужно сделать, это определить ваше владение памятью конкретно.
Container::set
принимает экземпляр Abstract
по ссылке, которая обычно не подразумевает передачу права собственности:
void set(Abstract &obj){...} // Caller retains ownership of obj, but now we have a weak reference to it
Тогда ответственность за удаление не на вас.
Container::get
возвращает указатель что подразумевает владение, указывая, что кто-то, кто звонит set
не должен лишать законной силы переданный объект.
Abstract* get(){...}
Как вы уже сказали, это может быть проблематично.
У вас есть несколько вариантов
Container
с соответствующей документацией (Код по контракту)std::shared_ptr
В первом случае, работает ли он или нет, зависит от того, читает ли пользователь и понимает ли ваш API, а затем ведет себя хорошо с ним. В последнем случае объект-указатель владеет собой и удаляет выделенную память, когда последний экземпляр выходит из области видимости.
void set(std::shared_ptr<Abstract> obj){...}
// now Container participates in the lifetime of obj,
// and it's harder to nullify the underlying object
// (you'd have to be intentionally misbehaving)
Если вы беспокоитесь о том, что объект может быть удален в другом месте, что может привести к появлению свисающего указателя, вы можете использовать умные указатели наддува.
Повышение умных указателей предоставит вам услугу бухгалтерского учета и поможет избежать такого случая.
Некоторая информация может быть найдена здесь:
умные указатели (повышение) объяснил
Это то, что std::unique_ptr
для:
class Container
{
std::unique_ptr<Abstract> thing;
public:
void set(std::unique_ptr<Abstract> obj)
{
thing = obj;
}
Abstract* get()
{
return thing.get();
}
};
Теперь Abstract
объект «принадлежит» Container
и будет очищен автоматически, когда Conatiner
уничтожен
Если вы хотите указатель, который может жить дольше или может использоваться несколькими контейнерами, используйте std::shared_ptr
вместо.