35

是否std::is_move_constructible<T>::value == true暗示T有一个可用的移动构造函数?如果是这样,它的默认行为是什么?

考虑以下情况:

struct foo {
    int* ptr;
};

int main() {
    {       
        std::cout << std::is_move_constructible<foo>::value << '\n';
        foo f;
        f.ptr = (int*)12;
        foo f2(std::move(f));
        std::cout << f.ptr << ' ' << f2.ptr << '\n';
    }
    return 0;
}

输出是:

1
0000000C 0000000C

我认为f.ptr应该是nullptr。所以在这种情况下,

  1. f2移动构建了吗?
  2. 如果是这样,不应该使右值无效吗?
  3. 我怎么知道一个类的实例是否可以正确移动构造(使旧实例无效)?

(我正在使用 VS11。)

更新

移动构造函数的默认行为与复制构造函数相同,是否正确?如果是真的,

  1. 我们总是期望一个移动 ctor 窃取被移动对象的资源,而默认的行为并不像预期的那样,那么拥有一个默认的移动 ctor 有什么意义呢?
  2. 我怎么知道一个类是否有一个自定义的移动构造函数(可以保证行为正确)?

当我声明一个时,它似乎foo f2(std::move(f));调用了复制 ctor,请参阅:

struct foo {
    int* ptr;
    foo() {}
    foo(const foo& other) {
        std::cout << "copy constructed\n";
    }
};

int main() {
    {       
        std::cout << std::is_move_constructible<foo>::value << '\n';
        foo f;
        foo f2(std::move(f));
    }
    system("pause");
    return 0;
}

现在输出是:

1
copy constructed

如果foo有一个移动构造函数,那么不会foo f2(std::move(f))调用它吗?

所以现在我的问题是:如何知道一个类是否有一个移动 ctor,如果有,我该如何明确地调用它?

我想做的是……</h2>
template<typename T, bool has_move_ctor>
struct MoveAux;

template<typename T>
struct MoveAux<T, true> {
    static void doMove(T* dest, T* src) {
        new(dest) T(std::move(*src)); //move ctor
    }
};

template<typename T>
struct MoveAux<T, false> {
    static void doMove(T* dest, T* src) {
        new(dest) T(*src); //copy ctor
        src->~T();
    }
};

template<typename T>
inline doMove(T* dest, T* src) {
    MoveAux<T,/*a trait*/>::doMove(dest, src);
}

所以我认为std::is_move_constructible<T>::value可以传递给模板,而现在我看到这个特征只关心是否T t(T())是一个有效的表达式,它可能会调用T::T(const T&). 现在假设这T是一个自定义类,那么我希望上面的模板表现得像:

  1. 如果我不声明移动 ctor,我希望该模板方法调用MoveAux<T,false>::doMove.
  2. 如果我声明了一个,我需要它调用MoveAux<T,true>::doMove.

有可能完成这项工作吗?

4

6 回答 6

24

是否std::is_move_constructible<T>::value == true暗示T有一个可用的移动构造函数?

移动构造函数或复制构造函数。请记住,复制构造的操作满足操作移动构造的所有要求,甚至更多。

在标准术语中,MoveConstructible对象是一个对象,其表达式的评估:

T u = rv; 

u相当于rv建造前的价值;移出rv 的状态未指定。但是由于它是未指定的,这意味着状态甚至可能与被移动rv之前状态相同:换句话说,u可能rv.

事实上,标准将概念定义为对概念CopyConstructible改进MoveConstructible(所以一切都是CopyConstructibleMoveConstructible但反之亦然)。

如果是这样,它的默认行为是什么?

隐式生成的移动构造函数的行为是对生成它的类型的数据成员执行逐个成员移动。

根据 C++11 标准的第 12.8/15 段:

非联合类的隐式定义的复制/移动构造函数X执行其基类和成员的成员复制/移动。[注意:非静态数据成员的大括号或等号初始化器被忽略。另见 12.6.2 中的示例。——尾注]

而且:

1 -f2移动构建?

是的。

2 - 如果是这样,不应该使右值无效吗?

移动指针与复制指针相同。所以没有失效发生,也不应该发生。如果您想要一个移动构造函数,使移动对象处于特定状态(即将指针数据成员设置为nullptr),您必须编写自己的 - 或将此责任委托给某些智能指针类,例如std::unique_ptr.

请注意,“无效”一词在这里并不完全正确。移动构造函数(以及移动赋值运算符)旨在使移出对象处于有效(但未指定)状态。

换句话说,需要尊重类不变量 - 并且应该可以在从对象上调用对其状态没有任何先决条件的操作(通常是销毁和赋值)。

于 2013-04-17T08:38:32.553 回答
5

std::is_move_constructible::value == true 是否暗示 T 有一个可用的移动构造函数?

不,它声明您可以采用对象类型的右值表达式并从中构造一个对象。这是否使用移动构造函数或复制构造函数与此特征无关。

f2 move 构造了吗?

是的。

如果是这样,不应该使右值无效吗?

不,这不是运动的运作方式。

我怎么知道一个类的实例是否可以正确移动构造(使旧实例无效)?

这不是存在的“正确移动构造”的任何定义。如果你想“使旧的无效”,那么你必须自己做。

移动构造通常保证旧对象的状态。它将处于有效但未定义的状态。这种状态很可能“和以前一样”。指针的移动构造与复制指针相同。

如果您想在移动后“无效”,那么您需要编写自己的移动构造函数来明确执行此操作。

(我正在使用 VS11)

那么你根本就没有编译器生成的移动构造函数。没关系,因为指针的移动和复制构造函数都做同样的事情。

于 2013-04-17T08:38:53.170 回答
3

移动构造函数的默认行为与复制构造函数相同,是否正确?如果这是真的

不,这是错误的。这仅适用于基元。它类似于复制构造函数。

默认生成的复制构造函数按照声明的顺序调用其所有成员的复制构造函数

但是默认生成的move构造函数会按照声明的顺序调用其所有成员的move构造函数

现在下一个问题是,原语ints floats的复制/移动构造函数pointer是做什么的?

答:他们只是复制值(复制和移动构造函数)

于 2013-04-17T13:08:50.320 回答
2

请注意,Visual Studio 2012 / VC++11 不支持编译器生成的移动构造函数;事实上,请考虑“Visual C++ 11 中的 C++11 特性”博客文章(强调我的)中的这句话:

右值引用 v3.0 添加了新规则,可以在特定条件下自动生成移动构造函数和移动赋值运算符。这不会在 VC11 中实现,它将继续遵循 VC10从不自动生成移动构造函数/移动赋值运算符的行为。

使用raw pointers,您必须自己定义移动构造函数,手动清除旧的“moved-from”指针:

class Foo 
{
public:

    // Move constructor
    Foo(Foo&& other)
        : m_ptr(other.m_ptr) // copy pointer value
    {
        // Clear out old "moved-from" pointer, to avoid dangling references
        other.m_ptr = nullptr;
    }

private:
    int* m_ptr;
};

相反,如果您使用类似的智能指针std::unique_ptr,则正确定义了移动构造函数,您可以调用std::move

class Foo 
{
public:

    // Move constructor
    Foo(Foo&& other)
        : m_ptr(std::move(other.m_ptr)) // move from other, 
                                        // old pointer automatically cleared
    {
    }

private:
    std::unique_ptr<int> m_ptr;
};

使用自动生成的移动构造函数,您不必显式定义自定义移动构造函数,如果您可以按成员移动。

于 2013-04-17T08:54:30.110 回答
0

n3376 12.8/15

非联合类 X的隐式定义的复制/移动构造函数执行其基类和成员的按成员复制/移动

每个基本或非静态数据成员都以适合其类型的方式复制/移动:

— 如果成员是一个数组,每个元素直接用 x 的对应子对象初始化;

— 如果成员 m 具有右值引用类型 T&&,则使用 static_cast(xm) 直接初始化它;

— 否则,基数或成员直接用 x 的相应基数或成员初始化。

于 2013-04-17T08:39:15.090 回答
0

如果 foo 有一个移动构造函数,那么 foo f2(std::move(f)) 不会调用它吗?当您提供复制构造函数时,您不会获得默认的移动构造函数。添加以下行以获取它(并注意更改)。foo(foo&& ifm)=默认值;

于 2013-11-29T19:41:15.987 回答