Rvalue引用和构造函数

Rvalue references and constructors

本文关键字:构造函数 引用 Rvalue      更新时间:2023-10-16

我阅读了以下关于右值引用的文章http://thbecker.net/articles/rvalue_references/section_01.html

但是有些事情我不理解。

这是我使用的代码:

#include <iostream>
template <typename T>
class PointerHolder
{
public:
    // 1
    explicit PointerHolder(T* t) : ptr(t) 
    {
        std::cout << "default constructor" << std::endl;
    }
    // 2
    PointerHolder(const PointerHolder& lhs) : ptr(new T(*(lhs.ptr)))
    {
        std::cout << "copy constructor (lvalue reference)" << std::endl;
    }
    // 3
    PointerHolder(PointerHolder&& rhs) : ptr(rhs.ptr)
    {
        rhs.ptr = nullptr;
        std::cout << "copy constructor (rvalue reference)" << std::endl;
    }   
    // 4
    PointerHolder& operator=(const PointerHolder& lhs)
    {
        std::cout << "copy operator (lvalue reference)" << std::endl;
        delete ptr;
        ptr = new T(*(lhs.ptr));
        return *this;
    }
    // 5
    PointerHolder& operator=(PointerHolder&& rhs)
    {
        std::cout << "copy operator (rvalue reference)" << std::endl;
        std::swap(ptr, rhs.ptr);
            return *this;
    }
    ~PointerHolder()
    {
        delete ptr;
    }
private:
    T* ptr;
};
PointerHolder<int> getIntPtrHolder(int i)
{
    auto returnValue = PointerHolder<int>(new int(i));
    return returnValue;
}

如果我评论构造函数2和3,编译器会说:

error: use of deleted function ‘constexpr PointerHolder<int>::PointerHolder(const PointerHolder<int>&)’
  auto returnValue = PointerHolder<int>(new int(i));
                                                  ^
../src/rvalue-references/move.cpp:4:7: note: ‘constexpr PointerHolder<int>::PointerHolder(const PointerHolder<int>&)’ is implicitly declared as deleted because ‘PointerHolder<int>’ declares a move constructor or move assignment operator

但是,如果我取消注释这两者中的任何一个,它就会编译并执行以下内容:

default constructor

以下是我的问题:

  • 当构造函数2和3被注释时,它尝试调用构造函数2。为什么?我希望它调用构造函数1,这就是我取消注释它们时它所做的
  • 关于这个错误:我声明了一个"move"复制操作符,这意味着我的对象可以从右值引用中"移动"复制。但是为什么它会隐式地删除我的普通复制构造函数呢?如果是这样,为什么它允许我通过明确定义并使用它来"取消删除"它

当构造函数2和3被注释时,它尝试调用构造函数2。为什么?

因为您的声明从临时对象初始化returnValue,所以该临时对象需要是可移动的或可复制的,使用move或copy构造函数。当您注释掉这些,并通过声明移动赋值运算符来禁止它们的隐式生成时,它们不可用,因此不允许初始化。

实际的移动或复制应该被忽略,这就是为什么在取消注释它们时只看到"默认构造函数"的原因。但是,即使取消了,也必须提供适当的构造函数。

为什么它会隐式删除我的普通复制构造函数?

通常,如果您的类具有时髦的移动语义,那么默认的复制语义将是错误的。例如,它可能会复制一个指向对象的指针,而该对象本应仅由类的单个实例指向;这又可能导致双重删除或其他错误。(事实上,您的move构造函数正是这样做的,因为您忘记了取消参数的指针)。

删除复制函数,并让您在需要时正确实现它们,比生成几乎肯定会导致错误的函数更安全。

如果是这样,为什么它允许我通过明确定义并使用它来"取消删除"它?

因为您通常想要实现复制语义和移动语义。

请注意,更传统的做法是将3称为"移动构造函数",将5称为"移赋值运算符",因为它们是移动的,而不是复制它们的参数

因为您正在删除复制构造函数和行

auto returnValue = PointerHolder<int>(new int(i));

不是真正的赋值,而是调用复制构造函数来构建对象。两个副本构造函数中的一个(通过引用或通过右值)需要可用,才能成功地从该临时构造函数初始化对象。如果你把这两个都评论掉,那就没有运气了。

如果一切都可用,会发生什么?为什么不叫这些?

这是一种称为"复制省略"的机制,基本上到所有东西都可以正确地使用复制构造函数"初始化"returnValue时,编译器是一个聪明的男孩,并实现:

"哦,我可以这样初始化returnValue"

PointerHolder<int> returnValue(new int(i));

当一切都可用时,就会发生这种情况。


至于为什么move构造函数似乎克服了隐式复制构造函数,我找不到比这更好的解释了:https://stackoverflow.com/a/11255258/1938163

您需要一个复制或移动构造函数来构造返回值。

如果去掉了所有复制/移动构造函数和所有赋值运算符(使用默认生成的构造函数/运算符),代码将进行编译,但由于多次删除成员ptr而失败。

如果保留复制/移动构造函数和赋值运算符,由于复制省略(返回值优化),您可能看不到任何对构造函数的调用。

如果禁用复制省略(g++:-fno省略构造函数),则由于多次删除成员ptr,代码将再次失败。

如果您更正移动构造函数:

// 3
PointerHolder(PointerHolder&& rhs) : ptr(0)
{
    std::swap(ptr, rhs.ptr);
    std::cout << "copy constructor (rvalue reference)" << std::endl;
}

使用禁用的副本省略进行编译,结果可能是:

default constructor
copy constructor (rvalue reference)
copy constructor (rvalue reference)
copy constructor (rvalue reference)