我能否在不进行复制的情况下将std :: vector <char>重新解释为std :: vector <unsigned char>?

10

我有一个指向std::vector<char>的引用,我想将其作为参数传递给一个接受std::vector<unsigned char>的函数。 我能在不复制的情况下做到这一点吗?

我有以下函数并且它可以正常工作; 但是我不确定是否实际上发生了复制-有人可以帮助我理解吗? 是否可以使用std::move避免复制,或者它已经没有被复制了?

static void showDataBlock(bool usefold, bool usecolor,
            std::vector<char> &chunkdata)  
{
  char* buf = chunkdata.data();                      
  unsigned char* membuf = reinterpret_cast<unsigned char*>(buf); 
  std::vector<unsigned char> vec(membuf, membuf + chunkdata.size()); 
  showDataBlock(usefold, usecolor, vec);   
} 

我在考虑我可以写:

std::vector<unsigned char> vec(std::move(membuf),
                               std::move(membuf) + chunkdata.size());  

这是否有些过度?实际会发生什么?

std::vector<unsigned char> vec(membuf, membuf + chunkdata.size()); makes a copy of the data in chunkdata - M.M
2
根据showDataBlock的名称,它可能需要重新设计为接受通用的迭代器对。 - M.M
@WhiZTiM: 不是的,有第二个重载函数(没有显示)需要一个std::vector<unsigned char> 作为其第三个参数。不过我想知道为什么这个重载函数使用非 const 引用来接收它的第三个参数。如果另外一个重载函数也使用非 const 引用来接收它的参数,那么它可能会对参数进行修改,而所示代码无法将从 vecchunkdata 的修改进行复制。 - MSalters
@MSalters,请问在this中的强制类型转换是否会像该答案评论中OP所担心的那样抑制某些优化? - WhiZTiM
@Toby Speight,我认为更改标题实际上使人难以理解我的请求-也许可以用不同的方式进行修改。 - serup
1
@serup - 我已经再次编辑了标题;如果您仍然认为它没有帮助,您可以随时[编辑]自己的帖子。 - Toby Speight
6个回答

5
如果您有一个类型为std::vector<T1>v1,并且需要一个类型为std::vector<T2>v2,即使T1和T2类似(例如charunsigned char),也无法避免复制数据。
请使用标准库:
std::vector<unsigned char> v2;
std::copy(v1.begin(), v1.end(), std::back_inserter(v2));

唯一可行的方法是以某种方式仅使用一种类型:如果可能,从一开始就获取std::vector<T2>,或者从现在开始使用std::vector<T1>(也许添加一个处理它的重载)。或者创建可以处理任何[连续]容器的通用代码(模板)。

我认为reinterpret_cast和std::move应该可以避免复制
不,不行
请详细说明-为什么不行?

向量只能从相同类型的另一个向量中窃取资源(移动数据)。这就是其接口设计的方式。

要做到你想要的,您需要一个release()方法,该方法将释放底层数据的向量所有权并将其作为(唯一的)指针返回,并且一个移动构造函数/赋值运算符,该函数将从(唯一的)指针获取底层数据。 (即使这样,您仍然需要一个reinterpret_cast,这是...危险区域)

std::vector没有这些功能。也许它应该有。但它没有。


我认为reinterpret_cast和std::move应该使得避免复制成为可能。 - serup
@serup 不,它不能。 - bolov
据我所知,std::move()是一种转换,它产生一个右值引用到一个对象,以便从中移动,并且这是一种相当新的C++方式,可以避免复制。例如,使用移动构造函数,std::vector只需将其内部指向数据的指针复制到新对象中,使移动后的对象处于不正确的状态,从而避免复制所有数据。 参考来源: https://dev59.com/vXA75IYBdhLWcg3wOGPS#3413547 - serup
1
@serup 可能会这样,但实际上并没有。正如我所说的,std::vector 没有一个允许手动获取/释放其内部缓冲区所有权的接口。唯一的方法是从相同类型的 std::vector 移动到另一个向量中。这就是 std::vector 的设计方式。 - bolov
@serup 正如你所说,std::move 只是一种类型转换。因此,std::move 的结果被视为临时对象。当传递临时对象作为参数时,没有理由发出警告。如果支持移动操作,则对象将被移动。如果不支持移动操作,则对象将被复制。 - bolov
显示剩余5条评论

4
“是否可以使用std :: move来避免拷贝,或者是否已经没有进行拷贝操作? ”
你不能在两个不相关的容器之间移动数据。一个“std :: vector ”并不是一个“std :: vector ”,因此没有合法的方式在O(1)时间内将一个容器的内容“移动-转换”到另一个容器中。
您只能复制:
void showData( std::vector<char>& data){
    std::vector<unsigned char> udata(data.begin(), data.end());
    for(auto& x : udata)
        modify( x );
    ....
}

或者实时转换每次访问的内容...
inline unsigned char& as_uchar(char& ch){
    return reinterpret_cast<unsigned char&>(ch);
}

void showDataBlock(std::vector<char>& data){
    for(auto& x : data){
        modify( as_uchar(x) );
    }
}

这个解决方案看起来是正确的,但从性能角度来看可能不太好,所以我决定使用另一个解决方案。 - serup
性能调优的第一条规则是“测量”。然而,对于任何一个好的优化编译器(显然现代版本的Clang、GCC、MSVC、Intel都是),应该不会为转换生成任何代码... 然而,我无法评论优化的影响... MSalters多年前就回答了这个问题。他肯定更有资格评论这种情况是否会抑制某些优化。 - WhiZTiM
1
实际上,unsigned char&是一个特殊情况。基本上,为了让memcpy工作,unsigned char&必须能够与任何东西别名。因此,现有的modify(unsigned char&)函数已经阻止了此as_uchar()方法也会阻止的优化。 - MSalters

1
如其他人已经指出,除非更改showDataBlock,否则无法避免复制。
我认为你有两个选择:
1.扩展showDataBlock使其适用于signed charunsigned char(即将其变为模板);或者
2.不要将容器作为参数,而是使用迭代器范围。然后,如果value_typechar,可以使用特殊的迭代器将signed char逐个转换为unsigned char元素。

您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - serup

1
我猜你又编写了一个重载函数:-
showDataBlock(usefold, usecolor, std::vector<unsigned char> & vec);  

你试图将std::vector<T>转换为另一个std::vector<T2>

无法避免复制

每个std::vector都有自己的存储空间,粗略地说,它是一个原始指针。
重点是:你不能在多个std::vector之间共享这样的原始指针。
我认为这是设计上的限制。
我认为这是一件好事,否则会浪费CPU来跟踪。

代码...

std::move(membuf)

...移动原始指针=实际上什么也不做。(与传递membuf相同)

为了优化,您应该验证原因:为什么首先要从std::vector<char>转换为std::vector<unsigned char>

如果创建一个新的类C,可以表示为charunsigned char,是否更好?(例如C::getChar()C::getUnsignedChar(),可以...仅存储char但提供其非静态函数作为转换器)

如果这样做没有帮助,建议创建一个新的自定义数据结构。
当需要时,我经常这样做。

然而,在这种情况下,我认为它不需要任何优化。
对我来说没问题,除非它是关键性能代码。


3
这是有意设计的。当你想要共享存储空间时,可以使用 std::shared_ptr,而且你还可以同时使用两者:std::shared_ptr<std::vector<char>> - MSalters
1
@MSalters 先生说得好!std::shared_ptr<std::vector<char>> 是很酷的符号。......“这是有意而为之的。” <-- 如果我可以看到古老的C++社区会议文件,那就太好了。..... 顺便问一下,您是否在某些实际情况下使用它(shared_ptr<vector<T>>)?您是否封装它?我很好奇。 :) - javaLover
2
我想不起来具体的案例,但这并不是什么奇怪的代码。例如,当你有一个发送者和一个接收者,并且它们共享缓冲区时,你会期望接收者有一个 std::shared_ptr<vector<T>>,而发送者有一个 std::weak_ptr<std::vector<T>>。(发送者在接收者退出后不需要保持缓冲区的存活) - MSalters

-1

unsigned charchar是不相关的类型。我认为它们在这种情况下足够相似(相同大小的POD),可以通过重新解释整个模板类来摆脱它们。

static void showDataBlock(bool usefold, bool usecolor,
            std::vector<char> &chunkdata)  
{
  showDataBlock(usefold, usecolor, reinterpret_cast< std::vector<unsigned char>&>(chunkdata));   
}

然而,我倾向于发现这些问题是由于没有设计最佳架构所致。从更大的角度来看,了解这个软件应该做什么,以确定为什么需要处理有符号和无符号字符数据块。


-3
我最终做了这样的事情:
static void showDataBlock(bool usefold,bool usecolor, std::vector<char> chunkdata)
{                                                                                                                           
    std::vector<unsigned char>&cache = reinterpret_cast<std::vector<unsigned char>&>(chunkdata);                                              
    showDataBlock(usefold, usecolor, cache);    
}                                                                             

static bool showDataBlock(bool usefold,bool usecolor, std::vector<unsigned char> &chunkdata)   
{
    // showing the data
}

这个解决方案允许我将向量作为引用或正常方式传递,看起来它正在工作 - 如果它是最好的解决方案,我不知道,但是你们都提出了一些非常好的建议 - 谢谢大家

我同意我无法避免复制,所以我让复制通过正常参数传递完成

如果您认为这个解决方案有问题,请在评论中提供更好的解决方案,而不仅仅是点踩


这个解决方案基于他人的建议和反复试错,因此它是一个严肃的解决方案并且有效。 - serup
如果您投反对票,请写下原因 - 否则您并不是真正认真的。 - serup
1
std::vector<char>std::vector<unsigned char>是两个完全无关的类(尽管它们是从同一个模板生成的)。它们之间的reinterpret_cast是未定义行为。 - bolov
@bolov,未定义的行为是否与ref指针的传递方式有关? - serup

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