我制作了一个小型的阻止队列'类。令我恼火的是,我为传递到enqueue
成员函数的值创建了冗余代码。
以下两个函数执行相同的操作(除了rvalue使用std :: move将rvalue移动到实际的队列集合中),除了句柄lvalue和rvalue之外:
void enqueue(const T& item)
{
std::unique_lock<std::mutex> lock(m);
this->push(item);
this->data_available = true;
cv.notify_one();
}
void enqueue(T&& item)
{
std::unique_lock<std::mutex> lock(m);
this->push(std::move(item));
this->data_available = true;
cv.notify_one();
}
我的问题是,有没有办法将这两个函数结合起来,而不会失去对右值参考的支持。
答案 0 :(得分:15)
这是完美前进的典型例子。通过模板化函数(成员模板,如果这是一个成员函数)来做到这一点:
template <class U>
void enqueue(U&& item)
{
std::unique_lock<std::mutex> lock(m);
this->push(std::forward<U>(item));
this->data_available = true;
cv.notify_one();
}
说明:如果您将左值T
传递给enqueue
,U
将推断为T&
,而forward
会将其作为左值传递,你将获得你想要的复制行为。如果您将右移T
传递给enqueue
,U
将推断为T
,而forward
会将其作为右值传递给您,您将会得到你想要的移动行为。
这比“按值传递”方法更有效,因为您永远不会进行不必要的复制或移动。关于“按值传递”方法的缺点是函数接受任何东西,即使它是错误的。您可能会或可能不会在push
下获得级联错误。如果这是一个问题,您可以enable_if enqueue
限制它将实例化的参数。
根据评论进行更新
根据以下评论,这就是我理解的事情:
#include <queue>
#include <mutex>
#include <condition_variable>
template <class T>
class Mine
: public std::queue<T>
{
std::mutex m;
std::condition_variable cv;
bool data_available = false;
public:
template <class U>
void
enqueue(U&& item)
{
std::unique_lock<std::mutex> lock(m);
this->push(std::forward<U>(item));
this->data_available = true;
cv.notify_one();
}
};
int
main()
{
Mine<int> q;
q.enqueue(1);
}
这一切都很好。但是,如果你试图将双排队:
,该怎么办?q.enqueue(1.0);
这仍然有效,因为double可以隐式转换为int。但是,如果你不想让它工作怎么办?然后你可以像这样限制enqueue
:
template <class U>
typename std::enable_if
<
std::is_same<typename std::decay<U>::type, T>::value
>::type
enqueue(U&& item)
{
std::unique_lock<std::mutex> lock(m);
this->push(std::forward<U>(item));
this->data_available = true;
cv.notify_one();
}
现在:
q.enqueue(1.0);
结果:
test.cpp:31:11: error: no matching member function for call to 'enqueue'
q.enqueue(1.0);
~~^~~~~~~
test.cpp:16:13: note: candidate template ignored: disabled by 'enable_if' [with U = double]
std::is_same<typename std::decay<U>::type, T>::value
^
1 error generated.
但是q.enqueue(1);
仍然可以正常工作。即限制您的会员模板是您需要做出的设计决策。您希望U
enqueue
接受哪些内容?没有正确或错误的答案。这是一项工程判断。还有其他几种可能更合适的测试(例如std :: is_convertible,std :: is_constructible等)。对于你的应用来说,正确的答案可能完全没有约束,就像上面的第一个原型一样。
答案 1 :(得分:7)
在我看来,enqueue(const&)
和enqueue(&&)
只是enqueue_emplace
的一个特例。任何好的类似C ++ 11的容器都有这三个函数,前两个是第三个函数的特例。
void enqueue(const T& item) { enqueue_emplace(item); }
void enqueue(T&& item) { enqueue_emplace(std::move(item)); }
template <typename... Args>
void enqueue_emplace(Args&&... args)
{
std::unique_lock<std::mutex> lock(m);
this->emplace(std::forward<Args>(args)...); // queue already has emplace
this->data_available = true;
cv.notify_one();
}
这是一个简单但有效的解决方案,它应该与原始界面的行为相同。它也优于模板方法,因为您可以将初始化列表排入队列。
老帖子:只需按价值做旧的传递:
void enqueue(T item)
{
std::unique_lock<std::mutex> lock(m);
this->push(std::move(item));
this->data_available = true;
cv.notify_one();
}
enqueue
“拥有”它的参数,并希望将其移入队列。通过价值传递是正确的概念。
它只做一个副本和一个动作。不幸的是,对于没有优化移动构造函数的T
,这可能会很慢。我认为由于这个原因,标准库中的容器总是有两个重载。但另一方面,一个好的编译器可能会优化它。
答案 2 :(得分:2)
你看过std::forward
了吗?
如果你在你的功能中加入一些模板,它可能就是你所要求的......