【问题标题】:Pass lvalue to rvalue将左值传递给右值
【发布时间】:2013-02-02 23:59:34
【问题描述】:

我做了一个小的“阻塞队列”类。我为传递给 enqueue 成员函数的值创建了冗余代码,这让我很恼火。

这里有两个函数做同样的事情(除了右值使用 std::move 将右值移动到实际的队列集合中),除了分别处理左值和右值:

    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();
    }

我的问题是,有没有一种方法可以结合这两个函数,而不会失去对右值引用的支持。

【问题讨论】:

  • 我想你可以调用一个私有 impl 函数,它有一个 move 布尔参数,里面写着 if(move) this-&gt;push(std::move(item)); else this-&gt;push(std::move(item));。一个 if 语句在性能方面应该无关紧要

标签: c++ c++11 lvalue rvalue


【解决方案1】:

这是一个需要完美转发的经典例子。通过模板化函数来做到这一点(如果这是成员函数,则为成员模板):

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 传递给enqueueU 将推导出为T&amp;forward 将它作为左值传递,你将得到你的复制行为想。如果你将右值T 传递给enqueueU 将推导出为Tforward 会将它作为右值传递,你将得到你想要的移动行为。

这比“按值传递”方法更有效,因为您永远不会进行不必要的复制或移动。 “按值传递”方法的缺点是该函数接受任何内容,即使它是错误的。您可能会或可能不会在push 下遇到级联错误。如果这是一个问题,您可以enable_if enqueue 来限制它将使用哪些参数进行实例化。

根据评论更新

根据下面的 cmets,这是我理解的样子:

#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); 仍然可以正常工作。 IE。限制您的成员模板是您需要做出的设计决定。你想让enqueue 接受什么U?没有正确或错误的答案。这是一个工程判断。还有其他一些可能更合适的测试可用(例如 std::is_convertible、std::is_constructible 等)。可能对您的应用程序的正确答案是完全没有约束,就像上面最初的原型一样。

【讨论】:

  • 感谢您的解释。 'push' 函数实际上是我的类派生自 std::queue 类的成员函数。另外,你能解释一下 U 和 T 之间的关系是如何工作的吗?
【解决方案2】:

在我看来enqueue(const&amp;)enqueue(&amp;&amp;) 只是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“拥有”它的参数并希望将其移入队列。按值传递是正确的概念。

它只做一个复制和一个移动。不幸的是,对于没有优化移动构造函数的Ts,这可能会很慢。我认为出于这个原因,标准库中的容器总是有两个重载。但另一方面,一个好的编译器可能会将其优化掉。

【讨论】:

    【解决方案3】:

    你看过std::forward吗? 如果你在你的函数中加入一些模板,它可能会按照你的要求做......

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-09
      • 2018-12-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-20
      相关资源
      最近更新 更多