首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >下面的move构造函数代码安全吗?

下面的move构造函数代码安全吗?
EN

Stack Overflow用户
提问于 2014-01-25 09:46:23
回答 2查看 732关注 0票数 18

这是类X的移动构造函数

X::X(X&& rhs)
    : base1(std::move(rhs))
    , base2(std::move(rhs))
    , mbr1(std::move(rhs.mbr1))
    , mbr2(std::move(rhs.mbr2))
{ }

以下是我要注意的事情:

  1. ,我们从rhs迁移了两次,不能保证rhs处于有效状态。对于将rhs的相应成员移动到mbr1mbr2base2
  2. We're的初始化来说,这不是未定义的行为吗?但是既然rhs已经从(同样,不能保证它处于有效状态)中移动,为什么要这样做呢?

这不是我的代码。我在一个网站上找到的。这个移动构造函数安全吗?如果是这样,又是如何做到的呢?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2014-01-25 10:09:50

这大致就是隐式move构造函数通常的工作方式:每个基对象和成员子对象都是从rhs的相应子对象移动构造的。

假设base1base2X的基础,没有带X / X& / X&& / const X&的构造函数,那么它在编写时是安全的。当传递给基类初始化器时,std::move(rhs)将隐式转换为base1&& (分别为base2&&)。

编辑:当我在一个基类中有一个与X&&完全匹配的模板构造函数时,这个假设实际上已经让我困扰了几次。显式地执行转换会更安全(尽管非常学究):

X::X(X&& rhs)
    : base1(std::move(static_cast<base1&>(rhs)))
    , base2(std::move(static_cast<base2&>(rhs)))
    , mbr1(std::move(rhs.mbr1))
    , mbr2(std::move(rhs.mbr2))
{}

或者仅仅是:

X::X(X&& rhs)
    : base1(static_cast<base1&&>(rhs))
    , base2(static_cast<base2&&>(rhs))
    , mbr1(std::move(rhs.mbr1))
    , mbr2(std::move(rhs.mbr2))
{}

我认为,如果除了base1/base2/mbr1/mbr2.之外没有其他基类或成员,那么它应该准确地复制编译器为X(X&&) = default;隐式生成的内容

再次编辑: C++11§12.8/15描述了隐式成员式复制/移动构造函数的确切结构。

票数 16
EN

Stack Overflow用户

发布于 2014-01-25 10:14:31

这取决于继承层次结构。但是很有可能这段代码是好的。这是一个完整的演示,表明它是安全的(对于这个特定的演示):

#include <iostream>

struct base1
{
    base1() = default;
    base1(base1&&) {std::cout << "base1(base1&&)\n";}
};

struct base2
{
    base2() = default;
    base2(base2&&) {std::cout << "base2(base2&&)\n";}
};

struct br1
{
    br1() = default;
    br1(br1&&) {std::cout << "br1(br1&&)\n";}
};

struct br2
{
    br2() = default;
    br2(br2&&) {std::cout << "br2(br2&&)\n";}
};

struct X
    : public base1
    , public base2
{
    br1 mbr1;
    br2 mbr2;

public:
    X() = default;
    X(X&& rhs)
    : base1(std::move(rhs))
    , base2(std::move(rhs))
    , mbr1(std::move(rhs.mbr1))
    , mbr2(std::move(rhs.mbr2))
    { }
};

int
main()
{
    X x1;
    X x2 = std::move(x1);
}

哪一项应该输出:

base1(base1&&)
base2(base2&&)
br1(br1&&)
br2(br2&&)

在这里,您可以看到每个基座和每个成员恰好移动了一次。

记住:std::move并不是真的移动。这只是一个向右值的强制转换,仅此而已。

因此代码强制转换为右值X,然后将其向下传递给基类。假设基类看起来像我上面概述的那样,那么有一个隐式的类型转换到右值base1base2,这将移动构造这两个单独的基类。

另外,

请记住:移出对象处于有效但未指定的状态。

只要base1base2的移动构造函数不进入派生类并更改mbr1mbr2,那么这些成员仍然处于已知状态,并准备从中移动。没问题。

现在我确实提到了可能会出现问题。这是如何实现的:

#include <iostream>

struct base1
{
    base1() = default;
    base1(base1&& b)
         {std::cout << "base1(base1&&)\n";}
    template <class T>
    base1(T&& t)
        {std::cout << "move from X\n";}
};

struct base2
{
    base2() = default;
    base2(base2&& b)
        {std::cout << "base2(base2&&)\n";}
};

struct br1
{
    br1() = default;
    br1(br1&&) {std::cout << "br1(br1&&)\n";}
};

struct br2
{
    br2() = default;
    br2(br2&&) {std::cout << "br2(br2&&)\n";}
};

struct X
    : public base1
    , public base2
{
    br1 mbr1;
    br2 mbr2;

public:
    X() = default;
    X(X&& rhs)
    : base1(std::move(rhs))
    , base2(std::move(rhs))
    , mbr1(std::move(rhs.mbr1))
    , mbr2(std::move(rhs.mbr2))
    { }
};

int
main()
{
    X x1;
    X x2 = std::move(x1);
}

在这个例子中,base1有一个模板化的构造函数,它接受一个右值。如果此构造函数可以绑定到右值X,并且此构造函数将从右值X,移动,那么就有问题了:

move from X
base2(base2&&)
br1(br1&&)
br2(br2&&)

解决这个问题的方法是使用forward<base1>(rhs)而不是move(rhs) (虽然相对较少,但也不是非常罕见)

    X(X&& rhs)
    : base1(std::forward<base1>(rhs))
    , base2(std::move(rhs))
    , mbr1(std::move(rhs.mbr1))
    , mbr2(std::move(rhs.mbr2))
    { }

现在base1看到的是右值base1,而不是右值X,它将绑定到base1移动构造函数(假设它存在),因此您将再次获得:

base1(base1&&)
base2(base2&&)
br1(br1&&)
br2(br2&&)

一切再一次与这个世界融为一体。

票数 16
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/21345515

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档