Rvalue引用和构造函数

时间:2014-04-18 08:23:06

标签: c++ c++11 rvalue-reference

我阅读了以下关于右值参考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,这是我取消注释它时所做的!
  • 关于错误:我声明了一个“移动”复制操作符,这意味着我的对象可以从右值引用中复制“移动”。但为什么它会隐式删除我的普通拷贝构造函数呢?如果是这样,为什么它允许我通过明确定义并使用它来“取消删除”它?

3 个答案:

答案 0 :(得分:2)

  

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

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

应该省略实际的移动或复制,这就是为什么你只看到&#34;默认构造函数&#34;当你取消注释它们时。但即使被淘汰,也必须有适当的构造函数。

  

为什么会隐式删除我的普通拷贝构造函数?

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

删除复制功能更安全,如果需要,可以让你正确实现它们,而不是生成几乎肯定会导致错误的功能。

  

如果是这样,为什么它允许我去#le;取消删除&#34;它通过明确定义并使用它?

因为您经常希望实现复制语义以及移动语义。

请注意,调用3 a&#34;移动构造函数&#34;更常规。和5 a&#34;移动赋值运算符&#34;,因为它们移动而不是复制它们的参数。

答案 1 :(得分:2)

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

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

不是真正的赋值,它调用复制构造函数来构建对象。需要提供两个复制构造函数之一(通过引用或通过rvalue)才能成功初始化该临时对象。如果你对这两者都发表评论,那就没有运气了。

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

这是一种名为“copy elision”的机制,基本上当一切都可以通过复制构造函数“初始化”returnValue时,编译器是一个聪明的人并意识到:

  

“哦,我可以像这样初始化returnValue”

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

这正是当一切都可用时会发生的事情。


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

答案 2 :(得分:0)

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

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

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

如果禁用copy elision(g ++: - fno-elide-constructors),由于成员ptr的多次删除,代码将再次失败。

如果你更正了move-constructor:

// 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)