c++ - 将自身从拥有它的容器中删除的shared_ptr,有更好的方法吗?

标签 c++ design-patterns shared-ptr smart-pointers

我想做的基本上是将一堆任务对象排队到一个容器中,任务可以从队列中删除自己。但我也不希望对象在删除自身时被销毁,因此它可以继续完成正在做的任何工作。

因此,执行此操作的安全方法是在工作完成时调用 RemoveSelf(),或者使用 keepAlive 引用然后继续执行工作。我已经验证这确实有效,而 DoWorkUnsafe 总是会在几次迭代后崩溃。

我对这个解决方案不是特别满意,因为我必须记住在工作结束时调用 RemoveSelf(),或者记住使用 keepAlive code>,否则会导致未定义的行为。

另一个问题是,如果有人决定迭代 ownerList 并进行工作,则迭代器会在迭代时失效,这也是不安全的。

或者,我知道我可以将任务放入单独的“清理”队列中并分别销毁已完成的任务。但这种方法对我来说似乎更简洁,但有太多警告。

是否有更好的模式来处理这样的事情?

#include <memory>
#include <unordered_set>

class SelfDestruct : public std::enable_shared_from_this<SelfDestruct> {
public:
    SelfDestruct(std::unordered_set<std::shared_ptr<SelfDestruct>> &ownerSet)
        : _ownerSet(ownerSet){}

    void DoWorkUnsafe() {
        RemoveSelf();
        DoWork();
    }

    void DoWorkSafe() {
        DoWork();
        RemoveSelf();
    }

    void DoWorkAlsoSafe() {
        auto keepAlive = RemoveSelf();
        DoWork();
    }


    std::shared_ptr<SelfDestruct> RemoveSelf() { 
        auto keepAlive = shared_from_this();
        _ownerSet.erase(keepAlive);
        return keepAlive;
    };

private:

    void DoWork() {
        for (auto i = 0; i < 100; ++i)
            _dummy.push_back(i);
    }

    std::unordered_set<std::shared_ptr<SelfDestruct>> &_ownerSet;
    std::vector<int> _dummy;
};

TEST_CASE("Self destruct should not cause undefined behavior") {

    std::unordered_set<std::shared_ptr<SelfDestruct>> ownerSet;

    for (auto i = 0; i < 100; ++i)
        ownerSet.emplace(std::make_shared<SelfDestruct>(ownerSet));

    while (!ownerSet.empty()) {
        (*ownerSet.begin())->DoWorkSafe();
    }
}

最佳答案

有一个很好的设计原则,即每个类都应该有一个目的。应该存在一个“任务对象”来执行该任务。当你开始增加额外的责任时,你往往会陷入困惑。困惑可能包括必须记住在完成主要目的后调用某个方法,或者必须记住使用一种 hacky 解决方法来保持对象处于事件状态。困惑往往是设计考虑不周的表现。对困惑感到不满意充分说明了您优秀设计的潜力。

让我们回溯一下the real problem 。容器中存储有任务对象。容器决定何时调用每个任务。在调用下一个任务之前,必须从容器中删除该任务(这样就不会再次调用它)。在我看来,从容器中删除元素的责任应该落在容器身上。

因此,我们将重新设想您的类,避免出现“SelfDestruct”困惑。您的任务对象的存在是为了执行任务。它们可能是多态的,因此需要一个指向任务对象的指针容器而不是任务对象的容器。任务对象不关心它们是如何管理的;那是为别人工作。

class Task {
public:
    Task() {}
    // Other constructors, the destructor, assignment operators, etc. go here

    void DoWork() {
        // Stuff is done here.
        // The work might involve adding tasks to the queue.
    }
};

现在关注容器。容器(更准确地说,容器的所有者)负责添加和删除元素。所以就这么做吧。您似乎更喜欢在调用元素之前删除该元素。对我来说这似乎是个好主意,但不要试图将任务的删除作为抵押。相反,请使用辅助函数,将此逻辑保持在容器所有者的抽象级别。

// Extract the first element of `ownerSet`. That is, remove it and return it.
// ASSUMES: `ownerSet` is not empty
std::shared_ptr<Task> extract(std::unordered_set<std::shared_ptr<Task>>& ownerSet)
{
    auto begin = ownerSet.begin();
    std::shared_ptr<Task> first{*begin};
    ownerSet.erase(begin);
    return first;
}

TEST_CASE("Removal from the container should not cause undefined behavior") {

    std::unordered_set<std::shared_ptr<Task>> ownerSet;

    for (int i = 0; i < 100; ++i)
        ownerSet.emplace(std::make_shared<Task>());

    while (!ownerSet.empty()) {
        // The temporary returned by extract() will live until the semicolon,
        // so it will (barely) outlive the call to DoWork().
        extract(ownerSet)->DoWork();
        // This is equivalent to:
        //auto todo{extract(ownerSet)};
        //todo->DoWork();
    }

}

从一个角度来看,这与您的方法相比几乎是微不足道的改变,因为我所做的只是将责任从任务对象转移到容器的所有者。然而,随着这种转变,困惑就消失了。执行相同的步骤,但它们是有意义的,并且当移动到更合适的上下文时几乎是被迫的。简洁的设计往往会带来简洁的实现。

关于c++ - 将自身从拥有它的容器中删除的shared_ptr,有更好的方法吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61150237/

相关文章:

c++ - C++ 不是强制要求 (cond ? string_1 : string_2) initialize a string?

c++ - 使用线程池的 std::async 的 Visual C++ 实现是否合法

.net - 如何在 Winforms 中使用 Presentation Model?

c++ - shared_ptr 类型转换

c++ - 如何在不破坏现有应用程序的情况下更改 API 中 STL 容器的数据类型?

c++ - 错误 : invalid operand for instruction using indexed addressing and Clang

c# - ASP.NET MVC 4 中的 Global.asax.cs 文件采用什么模式?

java - 具有不同契约(Contract)的装饰器模式

c++ - 创建 shared_ptr 到堆栈对象

C++ shared_ptr 从派生方法返回这个