Как удалить объект, для которого значение use_count больше 1?

#c #c 11 #c 17 #c 14 #smart-pointers

#c #c 11 #c 17 #c 14 #интеллектуальные указатели

Вопрос:

У меня есть объект с несколькими shared_ptrs указателями на него, и его счетчик ссылок use_count в связывающем блоке управления больше 1.

Теперь я хочу деконструировать объект, но я не знаю, где все они shared_ptrs , поэтому я не могу найти и деконструировать их, прежде чем деконструировать объект.

Если я просто деконструирую объект, это приведет к тому, что shared_ptrs они станут зависшими. Следовательно, в этой ситуации, как удалить объект с use_count размером больше 1, но не иметь представления обо всех его shared_ptrs ?

Спасибо за любые предложения!

Комментарии:

1. Вам нужно перепроектировать свою программу, чтобы она не использовала объекты там, где не должна.

2. Сделайте некоторые из них weak_ptr s, чтобы объект мог умереть до того, как они это сделают?

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

4. @sorosh_sabz Это неплохая практика, она совершенно неправильная. Эти общие указатели не будут «регистрировать» это удаление и попытаются также удалить управляемый объект, что приведет к неопределенному поведению.

5. @sorosh_sabz — Если вы собираетесь публиковать опасные, неправильные и вводящие в заблуждение «решения», пожалуйста, сделайте это в разделе ответов, где они могут получить экспертную оценку, которую они заслуживают. Не злоупотребляйте разделом комментариев.

Ответ №1:

Если у вас есть доступ к коду Object класса и вы можете его изменить, вы можете выполнить следующие шаги (после этого вы можете сразу перейти к окончательному коду):

  1. Создайте специальную структуру Fields , содержащую все поля original Object .
  2. Сохранить Fields как поле указателя, выделенное для кучи p_ .
  3. В исходном Object классе сделайте все исходные поля ссылками, указывающими на поля Fields выделенного объекта кучи.
  4. Добавьте destroyed_ флаг bool, который отмечает, что Object он уже был уничтожен. Этот флаг становится true после первого вызова деструктора.
  5. В каждом методе проверяйте, что destroyed_ это неверно, в противном случае генерируйте исключение. Потому что ни один из методов не может быть использован, когда объект уже уничтожен. Вы также можете просто показать сообщение с некоторой ошибкой вместо того, чтобы вызывать исключение и возвращаться из метода, ничего не делая. Как справиться с этой ошибкой, зависит от вас.
  6. Внутри деструктора при первом вызове выполните всю очистку как обычно. И отметьте destroyed_ как true . Второй вызов деструктора должен просто завершиться молча из-за того, что destroyed_ уже true .
  7. Все конструкторы копирования и операторы присваивания должны быть реализованы как обычно. Пример в коде ниже.

Чтобы удалить объект до освобождения всех общих указателей, просто вызовите ptr->~Object(); destructor , вот ptr любой общий указатель, или используйте удобную функцию std::destroy_at , например std::destroy_at(ptr.get()); .

В приведенном ниже коде, если last DoSomething() не вызывается (попробуйте закомментировать его), программа завершается без исключения, хотя отображается предупреждение о повторном вызове деструктора.

Попробуйте онлайн!

 #include <iostream>
#include <memory>

class Object {
public:
    Object()
        : p_(new Fields{}), f0_(p_->f0_), f1_(p_->f1_) {}

    Object(Object const amp; o)
        : p_(new Fields{}), f0_(o.f0_), f1_(o.f1_) {}
    
    Object amp; operator = (Object const amp; o) {
        f0_ = o.f0_;
        f1_ = o.f1_;
        return *this;
    }

    void DoSomething() {
        if (destroyed_)
            throw std::runtime_error("Object already destroyed!");
        f0_  = 1;
        f1_  = std::to_string(f0_)   " ";
        std::cout << "DoSomething: '" << f1_ << "'" << std::endl;
    }

    ~Object() {
        if (destroyed_) {
            std::cout << "Called destructor of destroyed object..."
                << std::endl;
            return;
        }
        // Process fields cleanup here...
        delete p_;
        p_ = nullptr;
        destroyed_ = true;
    }

private:
    struct Fields {
        int f0_ = 0;
        std::string f1_;
    };

    Fields * p_ = nullptr;
    bool destroyed_ = false;

    int amp; f0_;
    std::string amp; f1_;
};

int main() {
    try {
        std::shared_ptr<Object> o0 = std::make_shared<Object>();
        {
            std::shared_ptr<Object> o1 = o0;
            o1->DoSomething();
            o1->DoSomething();
            // Call destructor when you don't need object.
            // Even if some shared_ptrs still use it.
            o1->~Object();
        }
        o0->DoSomething();
        return 0;
    } catch (std::exception const amp; ex) {
        std::cout << "Exception: " << ex.what() << std::endl;
        return -1;
    }
}
 

Вывод:

 DoSomething: '1 '
DoSomething: '1 2 '
Called destructor of destroyed object...
Exception: Object already destroyed!
 

Аналогичный код, приведенный выше, также может быть реализован с использованием C 17 std::optional вместо указателя кучи, это решение даже лучше, потому что оно не использует выделение кучи, все поля расположены внутри тела объекта (т. Е. Выделение стека).

Попробуйте онлайн!

 #include <iostream>
#include <memory>
#include <optional>

class Object {
public:
    Object()
        : p_(Fields{}), f0_(p_->f0_), f1_(p_->f1_) {}

    Object(Object const amp; o)
        : p_(Fields{}), f0_(o.f0_), f1_(o.f1_) {}
    
    Object amp; operator = (Object const amp; o) {
        f0_ = o.f0_;
        f1_ = o.f1_;
        return *this;
    }

    void DoSomething() {
        if (destroyed_)
            throw std::runtime_error("Object already destroyed!");
        f0_  = 1;
        f1_  = std::to_string(f0_)   " ";
        std::cout << "DoSomething: '" << f1_ << "'" << std::endl;
    }

    ~Object() {
        if (destroyed_) {
            std::cout << "Called destructor of destroyed object..."
                << std::endl;
            return;
        }
        // Process fields cleanup here...
        p_ = std::nullopt;
        destroyed_ = true;
    }

private:
    struct Fields {
        int f0_ = 0;
        std::string f1_;
    };

    std::optional<Fields> p_;
    bool destroyed_ = false;

    int amp; f0_;
    std::string amp; f1_;
};

int main() {
    try {
        std::shared_ptr<Object> o0 = std::make_shared<Object>();
        {
            std::shared_ptr<Object> o1 = o0;
            o1->DoSomething();
            o1->DoSomething();
            // Call destructor when you don't need object.
            // Even if some shared_ptrs still use it.
            o1->~Object();
        }
        o0->DoSomething();
        return 0;
    } catch (std::exception const amp; ex) {
        std::cout << "Exception: " << ex.what() << std::endl;
        return -1;
    }
}
 

Вывод:

 DoSomething: '1 '
DoSomething: '1 2 '
Called destructor of destroyed object...
Exception: Object already destroyed!
 

Ответ №2:

Как удалить объект, для которого значение use_count больше 1?

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

но я не знаю, где все эти shared_ptrs

Затем вы должны решить проблему, которая мешает вам знать все эти указатели.

Или вы могли бы подумать, что, возможно, есть причина, по которой эти указатели существуют, и что, возможно, объект еще не должен быть удален, поскольку он, по-видимому, используется какой-то частью программы.