移动语义是否C++ C 缺少的东西

Is move semantics in C++ something C is missing?

本文关键字:语义 是否 C++ 移动      更新时间:2023-10-16

我一直在SO和其他来源上搜索此事,但我无法解决这个问题。使用对C++来说有些新的右值和x值的资源(使用C++11)。

现在,我们 - C 程序员 - 在这里错过了什么吗?或者 C 中有相应的技术可以从这些资源效率中受益?

编辑:这个问题不是基于任何意见的。我只是无法描述我的问题。我要问的是,c中是否有相应的技术。

当然,在 C 语言中也有类似的技术。多年来,我们一直在用C语言做"移动语义"。

首先,C++中的"移动语义"基于一堆重载解析规则,这些规则描述了具有右值引用参数的函数在重载解析期间的行为。由于 C 不支持函数重载,因此此特定问题不适用于 C。您仍然可以在 C 中手动实现移动语义,方法是编写具有专用名称的专用数据移动函数,并在要移动数据而不是复制数据时显式调用它们。例如,对于您自己的数据类型struct HeavyStruct您可以使用适当的实现编写copy_heavy_struct(dst, src)move_heavy_struct(dst, src)函数。您只需手动选择在每种情况下最合适/最有效的一个即可调用。

其次,C++中隐式移动语义的主要目的是在深度复制不必要地效率低下的上下文中作为隐式深拷贝语义的替代方法。由于 C 没有隐式的深拷贝语义,因此问题甚至不会出现在 C 中.C 总是执行浅拷贝,这已经与移动语义非常相似。基本上,你可以把C看作是一种总是移动的语言。它只需要一些手动调整即可使其移动语义达到完美。

当然,从字面上重现C++移动语义的所有特征可能是不可能的,因为例如,不可能将 C 指针绑定到右值。但几乎所有东西都可以"模仿"。它只需要显式/手动完成更多的工作。

我不相信C缺少的是移动语义。这是导致移动语义的所有C++功能,在 C 中"缺失"。由于您无法执行调用函数来分配内存的自动结构复制,因此您没有用于自动复制复杂且昂贵的数据结构的系统。

当然,这是意图。C 是一种比 C++ 更轻量级的语言,因此创建自定义复制和赋值构造函数的复杂性并不意味着成为语言的一部分 - 您只需编写代码来执行需要作为函数完成的操作。如果你想要"深拷贝",那么你写一些东西来遍历你的数据结构并分配内存等。如果你想要浅拷贝,你可以写一些东西,将数据结构中的指针复制到另一个(也许将源的指针设置为NULL) - 就像移动语义构造函数一样。

当然,你只需要 C 中的 L 和 R 值(它在 = 符号的左侧或右侧),没有引用,显然也没有 R 值引用。这是通过使用地址(将事物转换为指针)在 C 中实现的。

因此,C缺少的并不是真正的移动语义,而是C++语言设计中附带的复杂构造函数和赋值运算符(等),使移动语义在该语言中很有用。像往常一样,语言根据其特征发展。如果您没有特征 A,并且特征 B 依赖于存在的功能 A,则您"不需要"特征 B。

当然,除了异常处理和常量引用[以及C++11中的R值引用,这实际上是允许您修改的常量引用]之外,我认为C++中没有任何主要功能无法通过C实现。它有时有点尴尬和混乱(并且在语法上不会那么漂亮,当你以错误的方式覆盖函数时,编译器不会给你整洁的错误消息,你需要手动投射指针,等等,等等)。[说完这样的话,有人会指出"你显然没有想到 X",但总体说法还是正确的——C 可以做 99.9% 你想用 C 做的事情]

No.你必须自己动手,但就像C++的其他功能(例如多态性)一样,你可以影响相同的语义,但有更多的编码:

#include<stdlib.h>
typedef struct {
size_t cap;
size_t len;
int* data;
} vector ;
int create_vector(vector *vec,size_t init_cap){
vec->data=malloc(sizeof(int)*init_cap);
if(vec->data==NULL){
return 1;
}
vec->cap=init_cap;
vec->len=0;
return 0;
}
void move_vector(vector* to,vector* from){
//This effects a move...
to->cap=from->cap;
to->len=from->len;
free(to->data);
to->data=from->data;//This is where the move explicitly takes place.
//Can't call destroy_vec() but need to make the object 'safe' to destroy.
from->data=NULL;
from->cap=0;
from->len=0;
}
void destroy_vec(vector *vec){
free(vec->data);
vec->data=NULL;
vec->cap=0;
vec->len=0;
}

请注意,在move_vector()中,data是如何从一个向量移动到另一个向量(嗯...)

的。 在对象之间处理资源的想法在C语言中很常见,最终相当于"移动语义"。C++只是将其形式化,清理并合并到重载中。

你甚至可能自己做过,但没有意识到,因为你没有它的名字。任何更改资源"所有者"的地方都可以解释为"移动语义"。

C 没有与移动语义直接等价的对应物,但在 C++ 中移动语义解决的问题在 C 中不太常见: 由于 c 也没有复制构造函数/赋值运算符,因此默认情况下副本是浅层的,而在 c++ 中,通常的做法是将它们实现为深层复制操作或首先阻止它们。

C 也没有析构函数和 RAII 模式,因此转移资源所有权的频率较低。

相当于C++移动语义的 C 是按值传递struct,然后继续丢弃原始对象而不破坏它,依赖于副本的销毁是正确的。

但是,这在 C 语言中非常容易出错,因此通常可以避免。我们在 C 中实际执行的最接近移动语义的是当我们在结构数组上调用realloc()时,依赖于按位副本等效于原始副本。同样,原件既不会被破坏,也不会再次使用。

C 样式复制和 C++ 移动语义之间的区别在于,移动语义会修改原始语义,以便可以安全地调用其析构函数。使用 C 按位复制方法,我们只是忘记了原始内容,并且不对其调用析构函数。

这些更严格的语义使移动语义C++比 C 样式的复制和忘记更容易、更安全地使用。C++移动语义的唯一缺点是,它比 C 样式的复制和忘记方法略慢:按元素而不是按位移动语义复制,然后继续修改原始内容,以便析构函数成为语义 noop(尽管如此,它仍然被称为)。C 样式复制并忘记用一个简单的memcpy()替换所有这些。

相关文章: