强制使用复制赋值运算符而不是移动赋值运算符

6
假设我有一个对象,它同时定义了复制和移动赋值运算符。当我写下这段代码时:
Object a(/*parameters 1*/);
/* some code */
a = Object(/*parameters 2*/);

第三行最可能调用移动赋值运算符。如何强制编译器使用复制赋值而非移动赋值?同样地,如何强制复制覆盖移动构造函数?我基本上是在寻求一个反向的std::move()。
为什么我想要这个呢?
1到3行之间的代码(我不能更改)获取了一些指向a字段的指针,因此a的内存布局不被改变很重要(这可能是移动所做的),而是被新值覆盖。此外,我的对象只是std::vector。当我移动分配它时,编译器将仅将其底层数组的指针重定向到rvalue向量的数组。当我复制分配它时(并且以前的向量a更长),那么它的底层数组将被覆盖,但它们的地址将保持不变。 "某些代码"不幸地存储像&a [2]这样的指针,它们可能不会改变。

1
a 的布局或位置不能被改变,只能改变其成员的值。 (“移动”实际上并没有移动任何东西,而 std::move 只是一种转换。) - molbdnilo
移动操作不会改变 a 的布局,它只会将新值移动到相应的位置,这正是您想要的吧? 移动和复制的区别在于数据的源是否更改。 - gct
2
给定 auto a = std::vector{1, 2, 3}; auto b = &a[0]; a = std::vector{1, 2};,在将 a 移动后,b 将不再指向 a[0]。但是,如果保留了 a 的存储空间(类似于 std::vector 的复制构造函数,但仅当存储空间不必增加时),则 b 仍然有效。我认为这就是这个问题想要表达的内容? - hegel5000
1
请您能否提供一个具体的代码示例来替换掉 /* some code */,以便我们更好地了解在移植时可能会出现的问题? - 463035818_is_not_a_number
这可能是使用 std::unique_ptr<T[]> 的一个用例。这样你就可以保证指针不会改变。 - super
显示剩余2条评论
3个回答

2

将左值传递给赋值运算符

一种方法是将一个左值而不是右值传递给你要赋值的对象的赋值运算符:

Object a(/*parameters 1*/);
Object b(/*parameters 2*/);
// ...
a = b; // <-- b is an lvalue, copy not move

这样,a复制赋值运算符将被选择。


将不希望移动的对象标记为 const

另一种方法是将不希望移动的对象标记为 const。即使您已经使用了 std::move() 进行赋值,这种方法也可以起作用,因为 const 限定的对象不会绑定到 rvalue 引用。因此,移动赋值运算符不会被选择。具体来说:

const Object b(/*parameters 2*/);
// ...
a = std::move(b); // <-- still copies, b is const

即使使用std::move(b)明确标记b为移动对象,a的复制赋值操作符仍然会被选择。

2
我的对象只是std::vector。当我移动分配它时,编译器将仅将其底层数组的指针重定向到rvalue vector的数组。当我复制分配它(并且先前的vector a更长)时,它的底层数组将被覆盖,但它们的地址将保持不变。 "一些代码"不幸地存储像&a [2]这样的指针,它们可能不会改变。
因此,您需要使用其他内容而不是复制分配,因为复制分配也不安全。
当然,这个例子可以工作:
auto a = std::vector{1, 2};
auto const b = std::vector{3, 4};

auto pointer = a.data() + 1; // points to second element

a = b; // copy

std::cout << *pointer; // prints 4?

这可能有效,但并非总是如此!

考虑以下内容:

auto a = std::vector{1, 2};
auto const b = std::vector{3, 4, 5}; // three elements!

auto pointer = a.data() + 1; // points to second element

a = b; // copy. a's buffer is too small, must reallocate

std::cout << *pointer; // points in the old buffer, invalid.

指向向量元素的指针再分配时不安全


那么你可以怎么做呢?

定义自己的操作:

struct Object {
    // ... stuff

    auto safe_assign(Object const& other) & -> void {
        mem1 = other.mem1;
        mem2 = other.mem2;

        // let 'mem_vec' be your memeber vector
        assert(mem_vec.size() == other.mem_vec.size());

        // safe copy, will never reallocate.
        std::copy(other.mem_vec.begin(), other.mem_vec.end(), mem_vec.begin());
    }
};

2

就像std::move接受一个对象并返回对它的右值引用一样,你可以创建一个copy函数,接受一个对象并返回对它的左值引用。代码如下:

template< class T >
constexpr std::remove_reference_t<T>& copy( T&& t ) noexcept
{
    return t;
}

你会这样使用它

a = copy(Object(/*parameters 2*/));

由于std::move并不会真正“移动”对象,所以你的copy函数也并没有真正地复制对象。没问题 :-) - Jarod42
统一初始化并不总是统一的,移动并没有移动任何东西,为什么不使用一个不复制任何东西的副本;) - NathanOliver

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接