【问题标题】:When to use Move Constructors/Assignments何时使用移动构造函数/赋值
【发布时间】:2012-06-18 04:56:20
【问题描述】:

我已搜索但找不到“何时”使用它们的答案。我只是一直听说它很好,因为它为我节省了额外的副本。我到处把它放在我上过的每一堂课中,但是对于某些课来说这似乎没有意义:S 我已经阅读了无数关于 LValues 和 RValues 以及 std::move vs. std::copy vs. memcpy 的教程与 memmove 等相比,甚至还阅读了 throw(),但我也不确定何时使用它。

我的代码如下:

struct Point
{
    int X, Y;

    Point();
    Point(int x, int y);
    ~Point();

    //All my other operators here..
};

然后我有一个类似的类数组(RAII 之类的东西):

class PA
{
    private:
        std::vector<Point> PointsList;

    public:
        PA();
        //Variadic Template constructor here..
        ~PA();
        //Operators here..
 };

我应该使用移动构造函数和复制构造函数吗?我在 Point Class 有它,但感觉很奇怪,所以我把它删除了。然后我在 PA 课上有了它,但我认为它不会做任何事情,所以我也删除了它。然后在我的位图类中,我的编译器抱怨有指针成员但没有重载,所以我这样做了:

//Copy Con:
BMPS::BMPS(const BMPS& Bmp) : Bytes(((Bmp.width * Bmp.height) != 0) ? new RGB[Bmp.width * Bmp.height] : nullptr), width(Bmp.width), height(Bmp.height), size(Bmp.size), DC(0), Image(0)
{
    std::copy(Bmp.Bytes, Bmp.Bytes + (width * height), Bytes);
    BMInfo = Bmp.BMInfo;
    bFHeader = Bmp.bFHeader;
}

//Move Con:
BMPS::BMPS(BMPS&& Bmp) : Bytes(nullptr), width(Bmp.width), height(Bmp.height), size(Bmp.size), DC(0), Image(0)
{
    Bmp.Swap(*this);
    Bmp.Bytes = nullptr;
}

//Assignment:
BMPS& BMPS::operator = (BMPS Bmp)
{
    Bmp.Swap(*this);
    return *this;
}

//Not sure if I need Copy Assignment?

//Move Assignment:
BMPS& BMPS::operator = (BMPS&& Bmp)
{
    this->Swap(Bmp);
    return *this;
}

//Swap function (Member vs. Non-member?)
void BMPS::Swap(BMPS& Bmp) //throw()
{
    //I was told I should put using std::swap instead here.. for some ADL thing.
    //But I always learned that using is bad in headers.
    std::swap(Bytes, Bmp.Bytes);
    std::swap(BMInfo, Bmp.BMInfo);
    std::swap(width, Bmp.width);
    std::swap(height, Bmp.height);
    std::swap(size, Bmp.size);
    std::swap(bFHeader, Bmp.bFHeader);
}

这是正确的吗?我做了什么坏事或错事吗?我需要 throw() 吗?我的赋值和移动赋值运算符真的应该是一样的吗?我需要复印作业吗?啊这么多问题:c 我问的最后一个论坛无法回答所有问题,所以我很困惑。最后我应该使用 unique_ptr 作为字节吗? (这是一个字节/像素数组。)

【问题讨论】:

    标签: c++ c++11 move-semantics


    【解决方案1】:

    Scott Meyer's blog 上有一些很棒的想法:

    首先,并不是所有的复制请求都可以被移动代替。只有右值的复制请求才有资格进行优化。其次,并非所有类型都支持比复制操作更有效的移动操作。一个例子是 std::array。第三,即使是支持高效移动操作的类型也可能仅在某些时候支持它们。恰当的例子:std::string。它支持移动,但在使用 SSO(小字符串优化)实现 std::string 的情况下,小字符串的移动与复制一样昂贵!

    也许,您可以相应地对您的类型进行分类,然后决定所有需要移动语义。请注意,编译器自动生成移动 ctor/赋值运算符存在限制,因此建议您牢记这些限制。当您明确指定移动成员时,这会有所帮助。

    对于没有明确指定移动成员的类,有一些麻烦。还有显式/隐式删除移动成员​​的问题,它禁止从右值复制。可以在 Stroustrup 题为 To Move or Not to Move 的论文中找到关于隐式生成移动成员的问题的一个非常有价值的来源。

    关于移动语义的异常处理,我建议 Dave Abraham 的帖子Exceptionally Moving

    当我有时间时,我会尝试用一些例子来回到这个答案。希望上述链接暂时能帮助您入门。

    【讨论】:

    • 这绝对让我开始思考。它没有回答一些我想知道具体答案的问题,但它足以让我开始为自己学习。非常感谢。
    【解决方案2】:

    首先:尽可能使用“零规则”。请参阅“The rule of three/five/zero”和“C-20”。

    因此:您的“奇怪”感觉是正确的:PointPA 不需要明确的复制/移动运算符。否则,dirkgentlydirvine 的答案及其参考资料是很好的阅读材料,可以更深入地理解。

    对于BMPS,提供明确的移动操作符当然是个好主意。

    【讨论】:

    • 感谢您简洁的回答,非常有帮助。第一个网络链接中易于记忆的教训说明了一切:“具有自定义析构函数、复制/移动构造函数或复制/移动赋值运算符的类应该专门处理所有权”
    【解决方案3】:

    虽然移动是一个非常好的工具,但它带来的不仅仅是速度。使用 move 您正在移动对象(显然)并且什么都没有留下(除了空的尸体,或者更准确地说是默认构造的对象)。这会迫使您在喜欢移动对象时更仔细地考虑程序的所有权和设计。移动不是对某些对象的多次访问或共享,而是清楚地迫使您考虑谁拥有该对象以及何时拥有该对象。

    正如 Bjarne Stroustrup 之前所说,我们应该停止分享所有内容并停止到处提供指针。如果使用指针,则使用 unique_ptr 而不是 shared_ptr,除非您绝对想要共享所有权(在许多情况下您不想共享所有权)。 Unique_ptr 和它只是移动(无论如何都删除了副本)构造函数是一个很好的例子,它应该提供移动而不是复制的对象。

    Move 很棒,编写移动构造函数是一个非常好的主意,当 msvc 赶上并允许在其他编译器生成的(复制/分配等)构造函数上删除/默认装饰器时会更好。尝试访问以前删除的成员之类的错误在这里非常有用,只是将一些构造函数设为私有对代码维护者来说不太明显。在复制是可以的但首选移动的情况下,编译器希望尽可能选择移动(即使用 vector.push_back 进行测试,如果合理,一些编译器将移动或 emplace_back,购买即时性能提升),因此即使在复制构造函数对象可能会自动选择定义的移动构造函数以提高性能(忽略目前正在激烈的所有 SSO 讨论)。 This is a decent answer to peruse

    boost 邮件列表上有一些关于移动/复制和按值/引用传递的优势/劣势的非常重要的线程,如果您正在寻找更多信息,它们都在谈论类似的问题。

    【讨论】:

    • 我非常感谢那个链接和解释。也谢谢你!链接绝对有帮助。
    猜你喜欢
    • 2020-03-22
    • 1970-01-01
    • 2016-05-19
    • 2016-09-13
    • 1970-01-01
    • 2021-05-31
    • 1970-01-01
    • 2015-03-03
    相关资源
    最近更新 更多