c++ 将对象移动到数据结构中而不创建两个对象

7z5jn7bk  于 2023-05-08  发布在  其他
关注(0)|答案(1)|浏览(181)

我创建一个对象,例如:

std::string s{"string"};

我想把这个对象添加到一个数据结构中(比如std::vector),而不占用两个对象的内存空间。
当我这样做时:

std::vector<std::string> vec;
vec.emplace_back(std::move(s));

std::cout<<"s = "<<s<<"\n";
std::cout<<"vec[0] = "<<vec[0]<<"\n";

s = "different string";

std::cout<<"\ns = "<<s<<"\n";
std::cout<<"vec[0] = "<<vec[0]<<"\n";

我得到这个输出:

s = 
vec[0] = string

s = different string
vec[0] = string

所以是的,它“擦除”了原始字符串中的数据,但是如果我能够给它赋值,这意味着对象仍然存在,如果对象仍然存在,这不意味着程序在内存中为两个对象和它们的所有成员变量保留了足够的空间吗?
即使我把字符串创建为右值,如下图所示,我相信它会调用构造函数,移动它,然后调用析构函数,这意味着内存中有两个对象。

vec.emplace_back("string");

基本上,我想知道,什么是最有效的方法,让一个对象到一个数据结构?

rqqzpn5f

rqqzpn5f1#

你说的有对也有错。
由于您使用了string,让我们考虑一个简单的string实现:

class string {
    char *data;
    unsigned currentLength;
    unsigned allocationSize;

    // a lot more complexity we don't care about for now elided here
};

因此,字符串对象本身只存储字符串的当前长度、分配的空间量以及指向字符串本身的存储的指针。
当你执行类似a = std::move(b);的操作时,它会将这些大小和指针从b复制到a,然后设置b中的值以指示它是空的。所以你暂时有两个字符串 * 对象 *,但你 * 没有 * 字符串数据本身的副本。因此,如果你有一个字符串(比如说)20兆字节长,这个赋值在任何时候都不需要40兆字节的内存。字符串对象可能占用24个字节,在这种情况下,在赋值之前,你有20兆字节+ 24字节,在赋值期间,你有20兆字节+ 48字节,而在b被销毁后,你会回到20兆字节+ 24字节。
不过还有一个问题。大多数std::string的实现都有所谓的“小字符串优化”。很多字符串都很短,用24个字节来跟踪它+堆上的一个内存块来存储字符串本身并不是很有效。
典型的当前处理器使用64位类型的指针,但它实际上最多只支持42位寻址(左右--因处理器而异)。
在大多数情况下,我们可以在该指针的一个字节中使用(例如)一个特殊值来表示它不是真正的指针。相反,我们做一些类似的事情:

class internalString {
    // some value that can't be here in a pointer:
     char const magic = 0x2b;
     char data[23];
};

class externalString {
    // same as the string shown previously
};

union string {
    internalString internal;
    externalString external;
};

从那里,我们查看第一个字节,看看它是否是表示内部字符串的“魔法”值。如果是,那么我们将其视为internalString,将数据存储在字符串对象本身中。
在本例中,std::move并不能真正完成很多任务。将数据从一个字符串移动到另一个字符串仍然需要将数据从一个字符串复制到另一个字符串,在移动过程中,我们有两个字符串对象占用一个字符串对象的两倍空间。但它只发生在弦很小的时候,所以我们通常不太在意。

免责声明

为了避免我之前没有弄清楚,我在这里简化了很多东西。可能有点太多了。但希望足够好,这样就很容易掌握手头的要点。

相关问题