【问题标题】:unique_ptr: transfer of ownership from a container with top() functions [duplicate]unique_ptr:从具有 top() 函数的容器转移所有权 [重复]
【发布时间】:2022-12-05 04:53:23
【问题描述】:

想象一下,我想将 std::unique_ptr 存储在 std::stackstd::priority_queue 中。

这些容器有一个top()-函数,它返回一个“const_reference”到顶部元素。这将是对 std::unique_ptr 的 const_reference。

当我们通常想要将所有权从一个std::unique_ptr 转移到另一个时,我们需要移动数据。因此,将其转换为带有 std::move 的右值引用类型。然后移动赋值运算符将完成这项工作(如果有的话)。

我希望到目前为止一切都是正确的。如有不妥请指正。

当我执行典型的 top/pop 组合时,我不能使用 std::move(priorityqueue.top()),因为 top() 返回“const_reference”。

我唯一的想法是抛弃 top() 的常量。我觉得这在某种程度上不是最好的解决方案。


问题:对于这种情况,转让 std::unique_ptr 所有权的正确方法是什么?


我写了下面的例子,只是一个例子,只是一个演示,以更好地理解这个主题,一个 MRE。手头的函数是“getTop2”,它应该从 std::priority_queue 顶部/弹出 2 个元素,然后将这 2 个 std::unique_ptrs 返回给调用者。

请参见:

// Demo example, MRE
#include <iostream>
#include <utility>
#include <memory>
#include <queue>
#include <vector>

struct Foo {
    int value{};
    Foo() { std::cout << "Calling Foo default constructor\n"; }
    Foo(int v) : value(v) { std::cout << "Calling Foo constructor with value " << v << '\n'; }
    ~Foo() { std::cout << "Calling Foo destructor for Foo with value " << value << "\n\n";
    }
};
struct FooDeleter {
    void operator ()(Foo* f) {
        std::cout << "\nCalling std::priority_queue custom deleter for Foo with value " << f->value << '\n';
        delete f;
    }
};

using UPtr = std::unique_ptr<Foo, FooDeleter>;
struct Comp { bool operator ()(const UPtr& n1, const UPtr& n2) { return n1->value < n2->value; } };
using PQueue = std::priority_queue<UPtr,std::vector<UPtr>, Comp>;


// ------------------------------------------------------------
std::pair<UPtr, UPtr> getTop2(PQueue& pq) {
    UPtr left = std::move(const_cast<UPtr&>(pq.top()));
    pq.pop();
    UPtr right = std::move(const_cast<UPtr&>(pq.top()));
    pq.pop();
    return { std::move(left), std::move(right) };
}
// ------------------------------------------------------------

// Demo example, MRE
int main() {
    PQueue pq{};
    pq.push(UPtr(new Foo(1)));
    pq.push(UPtr(new Foo(3)));
    pq.push(UPtr(new Foo(2)));
    pq.push(UPtr(new Foo(4)));
    std::cout << '\n';
    while (pq.size() > 1u) {
        auto [left, right] = getTop2(pq);
        pq.push(UPtr(new Foo(left->value + right->value)));
    }
    while (pq.size())
        pq.pop();
}

正确的“getTop2(pq);”应该如何功能被实现?

并且,作为一个旁节点:我觉得这样的解决方案比使用原始指针更糟糕。但这不是这里的问题。

【问题讨论】:

标签: c++ priority-queue unique-ptr


【解决方案1】:
// One way to implement the getTop2() function would be to use the const_cast operator to cast the const_reference returned by the top() member function of std::priority_queue to a non-const reference. This would allow you to use the std::move() function on the reference returned by top() to transfer ownership of the std::unique_ptr to a local variable in the getTop2() function. Here is an example of how you could implement the getTop2() function:

std::pair<UPtr, UPtr> getTop2(PQueue& pq) {
    UPtr left = std::move(const_cast<UPtr&>(pq.top()));
    pq.pop();
    UPtr right = std::move(const_cast<UPtr&>(pq.top()));
    pq.pop();
    return { std::move(left), std::move(right) };
}

// It is worth noting that using the const_cast operator in this way is not considered good practice, as it breaks the const-correctness of the code. A better approach would be to provide a non-const version of the top() member function that returns a non-const reference to the top element in the priority queue, which you can then use with std::move() to transfer ownership of the std::unique_ptr. This can be done by providing a custom container adaptor class that derives from std::priority_queue and overloads the top() member function to return a non-const reference. Here is an example of how you could do this:

template <typename T, typename Container = std::vector<T>, typename Compare = std::less<T>>
class NonConstPriorityQueue : public std::priority_queue<T, Container, Compare> {
public:
    using std::priority_queue<T, Container, Compare>::priority_queue;

    // Overload the top() member function to return a non-const reference
    typename Container::reference top() {
        return const_cast<typename Container::reference>(static_cast<const std::priority_queue<T, Container, Compare>&>(*this).top());
    }
};

// Use the NonConstPriorityQueue class instead of std::priority_queue
using PQueue = NonConstPriorityQueue<UPtr, std::vector<UPtr>, Comp>;

std::pair<UPtr, UPtr> getTop2(PQueue& pq) {
    UPtr left = std::move(pq.top());
    pq.pop();
    UPtr right = std::move(pq.top());
    pq.pop();
    return { std::move(left), std::move(right) };
}

// With this approach, you can use the std::move() function on the return value of the top() member function to transfer ownership of the std::unique_ptr to a local variable in the getTop2() function without using the const_cast operator.

【讨论】:

  • NonConstPriorityQueue 是个坏主意 - 它使得破坏 std::priority_queue 的内部不变量变得非常容易。不要搬起石头砸自己的脚。
  • 试试它 instend std::pair<UPtr, UPtr> getTop2(PQueue& pq) { UPtr left = std::move(const_cast<UPtr&>(pq.top())); pq.pop(); UPtr right = std::move(const_cast<UPtr&>(pq.top())); pq.pop();返回 { std::move(左), std::move(右) }; }
猜你喜欢
  • 2015-05-21
  • 2013-06-14
  • 2015-05-16
  • 1970-01-01
  • 2010-11-30
  • 2019-09-23
  • 2014-12-18
  • 2022-01-24
  • 2014-12-06
相关资源
最近更新 更多