У меня есть класс A, который имеет указатель на класс B в качестве одного из его полей:
class A {
private:
B *ptr;
public:
A(B*);
A();
~A();
}
A::A(B *ptr)
{
this->ptr = ptr;
}
A::A()
{
this->ptr = new B();
}
A::~A()
{
delete this->ptr;
}
Я хочу, чтобы деструктор удалял B только в том случае, если он был выделен новым, я могу сделать это только с помощью установленного флага, хранящего источник указателя (так что delete вызывается только в том случае, если ptr был выделен во время построения A), или есть более элегантное решение, т. е. могу ли я просто вызвать delete для любого указателя (я знаю, что если переданный указатель был выделен с использованием new, он также будет удален, но если указатель будет передан из области видимости в какой конструктор A называется, по-видимому, удаляя это неопределено)?
Более элегантное решение состоит в том, чтобы не иметь классов с разными моделями владения, в этом случае они принадлежат, а не принадлежат вашему классу. Не смешивайте эти две идеи в одном классе, так как это может запутать ваших пользователей / клиентов (нарушая принцип наименьшего удивления).
Либо создайте отдельные классы с различным поведением, которое вам требуется, либо выберите модель владения и заставьте своих клиентов придерживаться ее (либо передайте и передайте владение в конструкторе, отличном от заданного по умолчанию, либо не допускайте самостоятельного размещения).
или есть более элегантное решение
Нет, не совсем; необходимо четко задокументировать, кому принадлежит этот указатель. Похоже, что ваш класс делает, так что вы можете просто построить ptr
сам?
#include <memory>
class A {
private:
std::unique_ptr<B> ptr;
public:
A(some_data args)
: ptr(new B(args)) { };
A();
}
Если ваш класс не владеет указателем, это тоже нормально, просто не освобождайте его. В противном случае вам нужно будет установить флаг извне (т.е. dealloc_on_destruct
) знать, что делать, и это, скорее всего, станет грязным.
На вопрос нельзя ответить только с помощью предоставленного вами кода. Реальный вопрос заключается в том, почему вы можете принять указатель или нет, и кому принадлежит память, если указатель передан вам.
В зависимости от того, что ваш объект должен делать в вашем конкретном домене, существуют разные альтернативы. Например, если вы можете поделиться правами собственности с вызывающим абонентом, то вам следует взять shared_ptr
, если вы сохраните право собственности на объект, вы должны использовать unique_ptr
объяснив в интерфейсе, что ваш объект захватит владение. Если владение не должно быть общим, рассмотрите возможность передачи ссылки на объект (даже если внутри хранится указатель), в этом случае вы можете отделить владение от фактического использования:
// option 1: shared ownwership
class A {
std::shared_ptr<B> ptr;
public:
A( std::shared_ptr<B> const & p ) : ptr(p) {}
A() : ptr( std::make_shared<B>() ) {}
};
// option 2: grab ownership
class A {
std::unique_ptr<B> ptr;
public:
A( std::unique_ptr<B> p ) : ptr( std::move(p) ) {}
A() : ptr( new B(); }
};
// option 3: no ownership (unless we create the object)
// (separating concerns: ref to access the object, ptr to manage it)
class A {
std::unique_ptr<B> ptr; // only ownership
B * ref;
public:
A( B & b ) : ptr(), ref(&b) {}
A() : ptr( new B() ), ref(ptr.get()) {}
// Internally use 'ref' to access the object the destructor will delete it
// if needed
};
Помимо того, что все остальные говорят (избегайте этого дизайна): если вы абсолютно не можете избежать этого дизайна, и вам нужен этот забавный класс, который иногда владеет его B
а иногда нет, и что это зависит от того, какой конструктор вызывается, то вы можете использовать unique_ptr
(или же shared_ptr
) вот так:
void delete_it(B*) { std::default_delete<B>()(t); }
void do_nothing(B *t) {}
class A {
unique_ptr<B, void(*)(B*)> ptr;
public:
A(B *ptr) ptr(ptr, do_nothing) {}
A() ptr(new B(), delete_it) {}
// no need for destructor
// hence also no need to worry about copy constructor or assignment
};
Причина, по которой это лучше, чем «необработанный указатель плюс флаг», заключается в том, что вам не нужно реализовывать удаление и копирование (и перемещение в C ++ 11). Это может быть хуже, чем «необработанный указатель плюс флаг» для производительности, но в большинстве случаев вам не нужно об этом беспокоиться. Вы можете вернуться и сделать дополнительную работу, если это оправдано. Вам не нужно менять исходный интерфейс класса для переключения, хотя это, вероятно, нарушит бинарную совместимость.