C++ 移出C+中std priority_队列的元素+;11

C++ 移出C+中std priority_队列的元素+;11,c++,c++11,move-semantics,move-constructor,C++,C++11,Move Semantics,Move Constructor,最少的工作示例 #include <cassert> #include <list> #include <queue> //#define USE_PQ struct MyClass { const char* str; MyClass(const char* _str) : str(_str) {} MyClass(MyClass&& src) { str = src.str; src.str = nullptr;

最少的工作示例

#include <cassert>
#include <list>
#include <queue>
//#define USE_PQ

struct MyClass
{
    const char* str;
    MyClass(const char* _str) : str(_str) {}
    MyClass(MyClass&& src) { str = src.str; src.str = nullptr; }
    MyClass(const MyClass&) = delete;
};

struct cmp_func
{
    bool operator() (const MyClass&, const MyClass&) const
    {
        return true;
    }
};

typedef std::priority_queue<MyClass, std::vector<MyClass>, cmp_func> pq_type;

#ifdef USE_PQ
MyClass remove_front(pq_type& l)
{
    MyClass moved = std::move(l.top());
    // error from the above line:
    // use of deleted function ‘MyClass::MyClass(const MyClass&)’
    l.pop();
    return std::move(moved);
}
#else
MyClass remove_front(std::list<MyClass>& l)
{
    MyClass moved = std::move(l.front());
    l.erase(l.begin());
    return std::move(moved);
}
#endif

int main()
{
    const char* hello_str = "Hello World!";
    MyClass first(hello_str);
#ifdef USE_PQ
    pq_type l;
    l.push(std::move(first));
    MyClass moved = remove_front(l);
#else
    std::list<MyClass> l;
    l.push_back(std::move(first));
    MyClass moved = remove_front(l);
#endif
    assert(moved.str);
    assert(!first.str);
    return 0;
}
#包括
#包括
#包括
//#定义使用PQ
结构MyClass
{
常量字符*str;
MyClass(const char*_str):str(_str){}
MyClass(MyClass&&src){str=src.str;src.str=nullptr;}
MyClass(常量MyClass&)=删除;
};
结构cmp_func
{
布尔运算符()(常量MyClass&,常量MyClass&)常量
{
返回true;
}
};
typedef std::优先级队列pq类型;
#ifdef使用_PQ
MyClass拆卸前(pq类型和l)
{
MyClass moved=std::move(l.top());
//来自上一行的错误:
//已删除函数“MyClass::MyClass(const MyClass&)”的使用
l、 pop();
返回标准::移动(已移动);
}
#否则
MyClass删除前(标准::列表和l)
{
MyClass moved=std::move(l.front());
l、 擦除(l.begin());
返回标准::移动(已移动);
}
#恩迪夫
int main()
{
const char*hello_str=“hello World!”;
我的班级第一(你好);
#ifdef使用_PQ
pq_型l;
l、 推(标准::移动(第一));
MyClass moved=移除前(l);
#否则
std::列表l;
l、 推回(标准::移动(第一));
MyClass moved=移除前(l);
#恩迪夫
断言(moved.str);
断言(!first.str);
返回0;
}
所以这是可行的。现在删除第4行的注释符号,它说需要复制构造函数(我的被删除)。此外,它还缺少
运算符=
。问题:

  • 这里有什么区别
  • 这个问题能解决吗?如果是,怎么做;如果不是,为什么不

注意:您也可以使用boost的priority_队列作为您的答案,但我得到了相同的错误。

这似乎是
std::priority_队列设计中的一个疏忽。似乎没有直接将元素移出(而不是复制)的方法。问题是
top()
返回一个
常量T&
,因此无法绑定到
T&
。而
pop()
返回
void
,因此您也无法从中获得它

然而,有一个解决办法。可以保证优先级队列中的对象实际上不是
const
。它们是普通对象,队列只是不给它们可变的访问权。因此,这样做完全合法:

MyClass moved = std::move(const_cast<MyClass&>(l.top()));
l.pop();
MyClass moved=std::move(const_cast(l.top());
l、 pop();
正如@DyP在注释中指出的,您应该确保moved-from对象仍然可以传递给队列的比较器。我相信,为了保持队列的先决条件,它必须与以前进行相同的比较(这几乎是不可能实现的)

因此,您应该将
cast&top()
pop()
调用封装在函数中,并确保其间不会对队列进行任何修改。如果这样做,就可以合理地确定不会在moved from对象上调用比较器

当然,这样的功能应该有非常好的文档记录


请注意,无论何时为类提供自定义复制/移动构造函数,都应该提供相应的复制/移动赋值运算符(否则,该类的行为可能不一致)。因此,只需为类指定一个已删除的复制赋值运算符和一个适当的移动赋值运算符

(注意:是的,在某些情况下,您需要可构造的移动类,但不需要可分配的移动类,但这种情况非常罕见(如果您找到它们,您就会知道)。根据经验,始终同时提供ctor和分配op)

这里有什么区别

std::priority\u queue::top
返回一个
const value\u type&
,因此不能调用
std::move
(它需要
T&

MyClass删除\u前面(std::list&l)
{
MyClass moved=std::move(l.front());
l、 擦除(l.begin());
返回标准::移动(已移动);
}
std::list::front
有一个返回引用的重载,因此它可以绑定到
T&

MyClass remove_front(std::list<MyClass>& l)
{
    MyClass moved = std::move(l.front());
    l.erase(l.begin());
    return std::move(moved);
}

我不确定为什么
top
没有非常量重载(可能是标准中的疏忽?)。您可以使用
const\u cast
来回避这个问题,但请确保编写完整的注释来描述您正在做什么以及为什么要这样做。

没有非-(const ref)top()可能有一个很好的原因:修改对象将打破优先级队列不变。因此,const_cast技巧可能只有在您之后立即弹出时才起作用。

根据您要在优先级队列中存储的类型,Angew解决方案的另一种替代方案是,避免
const_cast
并消除一些自爆的机会,将元素类型包装如下:

struct Item {
    mutable MyClass element;
    int priority; // Could be any less-than-comparable type.

    // Must not touch "element".
    bool operator<(const Item& i) const { return priority < i.priority; }
};

这样,在
MyClass
的移动语义上就没有特殊的要求来保持无效对象上的顺序关系,并且不会有任何代码段使优先级队列的不变量无效。

排名靠前的答案看起来不错,但不幸的是,它与
-D\u GLIBCXX\u DEBUG
不兼容。例如:

#include <iostream>
#include <memory>
#include <queue>
#include <vector>

struct T {
  int x;
  std::shared_ptr<int> ptr;
  T(int x, std::shared_ptr<int> ptr) : x(x), ptr(ptr) {}
};
struct Compare {
  bool operator()(const T& x, const T& y) {
    return *x.ptr < *y.ptr;
  }
};
int main() {
  auto ptr1 = std::make_shared<int>(3);
  auto ptr2 = std::make_shared<int>(3);
  std::priority_queue<T, std::vector<T>, Compare> f;
  f.emplace(3, ptr1);
  f.emplace(4, ptr2);
  T moved = std::move(const_cast<T&>(f.top()));
  f.pop();
  std::cerr << moved.x << "\n";
}
#包括
#包括
#包括
#包括
结构T{
int x;
std::共享的ptr;
T(intx,std::shared_ptr ptr):x(x),ptr(ptr){}
};
结构比较{
布尔运算符()(常数T&x、常数T&y){
返回*x.ptr<*y.ptr;
}
};
int main(){
自动ptr1=std::使_共享(3);
自动ptr2=std::使_共享(3);
std::优先级队列f;
f、 安置(3,ptr1);
f、 安置(4,ptr2);
T moved=std::move(const_cast(f.top());
f、 pop();

std::cerr很容易扩展
std::priority_queue
,因为它将底层容器公开为受保护的成员:

template <
    class T,
    class Container = std::vector<T>,
    class Compare = std::less<typename Container::value_type>>
class extended_priority_queue : public std::priority_queue<T, Container, Compare> {
public:
  T top_and_pop() {
    std::pop_heap(c.begin(), c.end(), comp);
    T value = std::move(c.back());
    c.pop_back();
    return value;
  }

protected:
  using std::priority_queue<T, Container, Compare>::c;
  using std::priority_queue<T, Container, Compare>::comp;
};

priority\u queue::top()
返回常量引用,因此即使在move之后,它仍然是一个左值。@C.R.
std::move
只是对右值引用的转换,因此任何类型为
const T
的左值都将转换为
C
#include <iostream>
#include <memory>
#include <queue>
#include <vector>

struct T {
  int x;
  std::shared_ptr<int> ptr;
  T(int x, std::shared_ptr<int> ptr) : x(x), ptr(ptr) {}
};
struct Compare {
  bool operator()(const T& x, const T& y) {
    return *x.ptr < *y.ptr;
  }
};
int main() {
  auto ptr1 = std::make_shared<int>(3);
  auto ptr2 = std::make_shared<int>(3);
  std::priority_queue<T, std::vector<T>, Compare> f;
  f.emplace(3, ptr1);
  f.emplace(4, ptr2);
  T moved = std::move(const_cast<T&>(f.top()));
  f.pop();
  std::cerr << moved.x << "\n";
}
template <
    class T,
    class Container = std::vector<T>,
    class Compare = std::less<typename Container::value_type>>
class extended_priority_queue : public std::priority_queue<T, Container, Compare> {
public:
  T top_and_pop() {
    std::pop_heap(c.begin(), c.end(), comp);
    T value = std::move(c.back());
    c.pop_back();
    return value;
  }

protected:
  using std::priority_queue<T, Container, Compare>::c;
  using std::priority_queue<T, Container, Compare>::comp;
};
template<typename PriorityQueue>
auto priority_queue_top_and_pop(PriorityQueue& queue) ->
    typename PriorityQueue::value_type {
  return static_cast<extended_priority_queue<
      typename PriorityQueue::value_type,
      typename PriorityQueue::container_type,
      typename PriorityQueue::value_compare>&>(queue).top_and_pop();
}