Указатель на объект стека без права собственности
Я хочу иметь класс с переменной-указателем. Этот указатель должен указывать на объект, который может быть выделен в стек или выделен в виде кучи. Однако этот указатель не должен владеть. Другими словами, никакого удаления не следует вызывать вообще, когда указатель выходит из области видимости. Я думаю, что необработанный указатель может решить проблему... Однако я не уверен, есть ли более хороший подход на С++ 11, чем исходные указатели?
Пример:
class foo{
public:
bar* pntr
};
int main(){
bar a;
foo b;
b.pntr=&a;
}
Ответы
Ответ 1
Необработанные указатели здесь превосходны. У С++ 11 нет другого "немого" умного указателя, который имеет дело с объектами, не имеющими права собственности, поэтому вы не можете использовать интеллектуальные указатели С++ 11. Существует предложение для "глупых" умных указателей для не принадлежащих ему объектов:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4282.pdf
уже реализован экспериментально как std::experimental::observer_ptr
(спасибо @T.C. за подсказку).
Другой альтернативой является использование интеллектуального указателя с пользовательским удалением, который ничего не делает:
#include <memory>
int main()
{
int a{42};
auto no_op = [](int*){};
std::unique_ptr<int, decltype(no_op)> up(&a, no_op);
}
или, как упоминалось в @T.C. в комментарии, std::reference_wrapper
.
Как упоминалось в @Lightness Races на орбите, std::weak_ptr
также может быть решением, так как последний также является не владеющим умным указателем. Однако a std::weak_ptr
может быть построено только из std::shared_ptr
или другого std::weak_ptr
. Серьезным недостатком является то, что std::shared_ptr
является "тяжелым" объектом (из-за внутреннего механизма подсчета ссылок). Обратите внимание, что даже в этом случае std::shared_ptr
должен иметь тривиальный пользовательский делектор, иначе он повреждает стек для указателей на автоматические переменные.
Ответ 2
Использование исходного указателя здесь совершенно нормально, так как вы не намерены позволять указателю владеть указанным ресурсом.
Ответ 3
Если под "лучшим подходом" вы подразумеваете "более безопасный подход", то да, я реализовал "не владеющий" умный указатель здесь: https://github.com/duneroadrunner/SaferCPlusPlus. (Shameless plug alert, но я думаю, что это актуально здесь.) Таким образом, ваш код будет выглядеть так:
#include "mseregistered.h"
...
class foo{
public:
mse::TRegisteredPointer<bar> pntr;
};
int main(){
mse::TRegisteredObj<bar> a;
foo b;
b.pntr=&a;
}
TRegisteredPointer "умнее", чем исходные указатели, поскольку он знает, когда цель уничтожается. Например:
int main(){
foo b;
bar c;
{
mse::TRegisteredObj<bar> a;
b.pntr = &a;
c = *(b.pntr);
}
try {
c = *(b.pntr);
} catch(...) {
// b.pntr "knows" that the object it was pointing to has been deleted so it throws an exception.
};
}
TRegisteredPointer обычно имеет более низкую стоимость, чем говорят, std:: shared_ptr. Гораздо ниже, когда у вас есть возможность выделить целевой объект в стеке. Он все еще довольно новый, хотя и не совсем документированный, но в библиотеку включены комментарии к ним (в файле "msetl_example.cpp", нижняя половина).
Библиотека также предоставляет TRegisteredPointerForLegacy, которая несколько медленнее, чем TRegisteredPointer, но может использоваться как замена для исходных указателей практически в любой ситуации. (В частности, его можно использовать до того, как целевой тип полностью определен, что не относится к TRegisteredPointer.)
С точки зрения чувства вашего вопроса, я считаю, что это действительно так. К настоящему времени программисты на С++ должны по крайней мере иметь возможность избежать ненужного риска недопустимого доступа к памяти. Исходные указатели также могут быть допустимым вариантом, но я думаю, что это зависит от контекста. Если это сложная часть программного обеспечения, где безопасность важнее производительности, более безопасная альтернатива может быть лучше.
Ответ 4
Проблема с необработанным указателем заключается в том, что нет способа определить, указывает ли он на действительный объект. К счастью, std::shared_ptr
имеет конструктор сглаживания , который можно эффективно использовать для члена std::weak_ptr
для члена класса с автоматическим временем хранения. Пример:
#include <iostream>
#include <memory>
using namespace std;
struct A {
int x;
};
void PrintValue(weak_ptr<int> wp) {
if (auto sp = wp.lock())
cout << *sp << endl;
else
cout << "Object is expired." << endl;
}
int main() {
shared_ptr<A> a(new A);
a->x = 42;
weak_ptr<int> wpInt (shared_ptr<int>(a, &a->x));
PrintValue(wpInt);
a.reset(); //a->x has been destroyed, wpInt no longer points to a valid int
PrintValue(wpInt);
return 0;
}
Печать
42
Объект истек.
Основное преимущество этого подхода состоит в том, что weak_ptr
не препятствует выходу объекта из области действия и удалению, но в то же время он может безопасно обнаруживать, когда объект больше недействителен. Недостатком является увеличение накладных расходов смарт-указателя и тот факт, что вам в конечном итоге нужен shared_ptr
для объекта. То есть вы не можете делать это исключительно с объектами, выделенными в стеке.
Ответ 5
Просто выделите объект динамически и используйте shared_ptr
. Да, это фактически удалит вещь, но только если она последняя с ссылкой. Кроме того, он не позволяет другим удалять его. Это именно то, что нужно сделать, чтобы избежать утечек памяти и оборванных указателей. Также проверьте связанный weap_ptr
, который вы, возможно, можете использовать и в ваших интересах, если требования к жизни для переводчика различны.