Указатель на объект стека без права собственности

Я хочу иметь класс с переменной-указателем. Этот указатель должен указывать на объект, который может быть выделен в стек или выделен в виде кучи. Однако этот указатель не должен владеть. Другими словами, никакого удаления не следует вызывать вообще, когда указатель выходит из области видимости. Я думаю, что необработанный указатель может решить проблему... Однако я не уверен, есть ли более хороший подход на С++ 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, который вы, возможно, можете использовать и в ваших интересах, если требования к жизни для переводчика различны.