这里的用法与直接在 C++ std:vector 中使用 read()相同,但需要重新分配。
输入文件的大小是未知的,因此当文件大小超过缓冲区大小时,缓冲区会通过加倍大小重新分配。这是我的代码:
#include <vector>
#include <fstream>
#include <iostream>
int main()
{
const size_t initSize = 1;
std::vector<char> buf(initSize); // sizes buf to initSize, so &buf[0] below is valid
std::ifstream ifile("D:\\Pictures\\input.jpg", std::ios_base::in|std::ios_base::binary);
if (ifile)
{
size_t bufLen = 0;
for (buf.reserve(1024); !ifile.eof(); buf.reserve(buf.capacity() << 1))
{
std::cout << buf.capacity() << std::endl;
ifile.read(&buf[0] + bufLen, buf.capacity() - bufLen);
bufLen += ifile.gcount();
}
std::ofstream ofile("rebuild.jpg", std::ios_base::out|std::ios_base::binary);
if (ofile)
{
ofile.write(&buf[0], bufLen);
}
}
}
该程序按预期打印向量容量,并写入与输入相同大小的输出文件,但在 offset 之前仅与输入相同的字节,initSize
之后全为零......
使用&buf[bufLen]
inread()
绝对是一种未定义的行为,但是&buf[0] + bufLen
因为保证了连续分配,所以得到了正确的写入位置,不是吗?(提供initSize != 0
。请注意std::vector<char> buf(initSize);
大小buf
为initSize
。是的,如果initSize == 0
,在我的环境中发生了一个正常的致命错误。)我错过了什么吗?这也是UB吗?标准是否说明了 std::vector 的这种用法?
是的,我知道我们可以先计算文件大小并分配完全相同的缓冲区大小,但是在我的项目中,可以预期输入文件几乎总是小于某个SIZE
,所以我可以设置initSize
并SIZE
期望没有开销(像文件大小计算),并仅将重新分配用于“异常处理”。是的,我知道我可以reserve()
用resize()
和capacity()
替换size()
,然后用很少的开销来工作(每次调整大小时将缓冲区归零),但我仍然想摆脱任何冗余操作,只是一种偏执狂......
更新1:
事实上,我们可以从得到正确位置的标准逻辑推导&buf[0] + bufLen
出来,考虑:
std::vector<char> buf(128);
buf.reserve(512);
char* bufPtr0 = &buf[0], *bufPtrOutofRange = &buf[0] + 200;
buf.resize(256); std::cout << "standard guarantees no reallocation" << std::endl;
char* bufPtr1 = &buf[0], *bufInRange = &buf[200];
if (bufPtr0 == bufPtr1)
std::cout << "so bufPtr0 == bufPtr1" << std::endl;
std::cout << "and 200 < buf.size(), standard guarantees bufInRange == bufPtr1 + 200" << std::endl;
if (bufInRange == bufPtrOutofRange)
std::cout << "finally we have: bufInRange == bufPtrOutofRange" << std::endl;
输出:
standard guarantees no reallocation
so bufPtr0 == bufPtr1
and 200 < buf.size(), standard guarantees bufInRange == bufPtr1 + 200
finally we have: bufInRange == bufPtrOutofRange
而这里的 200 可以替换为每个buf.size() <= i < buf.capacity()
类似的扣除成立。
更新2:
是的,我确实错过了一些东西......但问题不是连续性(见更新1),甚至不是写内存失败(见我的回答)。今天我有时间研究这个问题,程序得到了正确的地址,将正确的数据写入保留的内存,但在接下来reserve()
,buf
被重新分配并且只有范围内的元素被[0, buf.size())
复制到新的内存中。所以这就是整个谜语的答案......
最后说明:如果在缓冲区填充了一些数据后不需要重新分配,则绝对可以使用reserve()/capatity()
而不是resize()/size()
,但如果需要,请使用后者。此外,在此处可用的所有实现(VC++、g++、ICC)下,该示例按预期工作:
const size_t initSize = 1;
std::vector<char> buf(initSize);
buf.reserve(1024*100); // assume the reserved space is enough for file reading
std::ifstream ifile("D:\\Pictures\\input.jpg", std::ios_base::in|std::ios_base::binary);
if (ifile)
{
ifile.read(&buf[0], buf.capacity()); // ok. the whole file is read into buf
std::ofstream ofile("rebuld.jpg", std::ios_base::out|std::ios_base::binary);
if (ofile)
{
ofile.write(&buf[0], ifile.gcount()); // rebuld.jpg just identical to input.jpg
}
}
buf.reserve(1024*200); // horror! probably always lose all data in buf after offset initSize
这是另一个示例,引用自 'TC++PL, 4e' pp 1041,请注意函数中的第一行使用reserve()
而不是resize()
:
void fill(istream& in, string& s, int max)
// use s as target for low-level input (simplified)
{
s.reserve(max); // make sure there is enough allocated space
in.read(&s[0],max);
const int n = in.gcount(); // number of characters read
s.resize(n);
s.shrink_to_fit(); // discard excess capacity
}
更新3(8年后):这些年发生了很多事情,我已经将近6年没有使用C++作为我的工作语言,现在我是一名博士生!此外,尽管许多人认为存在 UB,但他们给出的原因却大相径庭(有些已经被证明不是 UB),这表明这是一个复杂的案例。因此,在投票和写答案之前,强烈建议阅读并参与评论。
另一件事是,通过博士培训,我现在可以相对轻松地深入研究 C++ 标准,这是我多年前不敢做的。我相信我在自己的回答中表明,根据标准,上述两个代码块应该可以工作。(该string
示例需要 C++11。)由于我的答案仍然存在争议(但我相信不是伪造的),我不接受它,而是对批评性评论和其他答案持开放态度。