如何将现有数据表示为std::vector



我必须将现有数据(已知大小的unsigned char内存区域)传递给期望const std::vector<std::byte>&的库函数。有没有办法";傻瓜;库函数相信它在对现有数据进行操作时收到了向量?

  1. 我将旧遗留的数据作为指针和大小,而不是std::vector。遗留的C代码通过malloc()分配内存,并提供指针和大小。请不要建议触碰遗留代码——在这句话结束时,我将不再是公司的员工。

  2. 我不想创建临时矢量和复制数据,因为内存吞吐量很大(>5GB/秒)。

  3. Placementnew创建矢量,但第一个字节用于矢量数据本身。我不能在内存区域之前使用几个字节——遗留代码没有预料到这一点(请参阅上文——内存区域是由malloc()分配的)。

  4. 更改第三方库是不可能的。它需要常量std::vectorstd::byte&-不跨越迭代器等

看起来我别无选择,只能使用临时矢量,但可能还有其他想法。。。我不在乎,但这是关于密集的视频处理,会有很多数据需要免费复制。

有什么办法"傻瓜;库函数相信它在对现有数据进行操作时收到了向量?

否。

潜在的选择包括:

  1. 将数据放在矢量中
  2. 或者将期望向量的函数更改为不期望向量
  3. 或者创建一个矢量并复制数据

如果1。和2。对你来说不是有效的选项,剩下3个。不管你愿不愿意。

正如上面提到的,这在标准C++中是不可能做到的。你不应该尝试这样做。

如果您可以容忍只使用libstdc++,并且可能会被特定的标准库版本卡住,那么看起来您可以这样做。同样,您不应该这样做。我写这个答案只是因为在这种特殊情况下,没有UB似乎是可能的。

当前版本的libstdc++似乎将其向量的重要成员公开为protected:https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bitsl/stl_vector.h#L422

您所需要做的就是从std::vector继承(这不是禁止的),编写自己的构造函数来设置这些受保护的成员,并编写一个析构函数来重置成员,这样实际的向量析构函数就不会删除您的内存。

#include <vector>
#include <cstddef>
template <class T>
struct dont_use_me_in_prod : std::vector<T>
{
dont_use_me_in_prod(T* data, size_t n) {
this->_M_impl._M_start = data;
this->_M_impl._M_finish = data + n;
this->_M_impl._M_end_of_storage = this->_M_impl._M_finish;
}  
~dont_use_me_in_prod() {
this->_M_impl._M_start = nullptr;
this->_M_impl._M_finish = nullptr;
this->_M_impl._M_end_of_storage = nullptr;
}
};
void innocent_function(const std::vector<int>& v);
void please_dont_do_this_in_prod(int* vals, int n) {
dont_use_me_in_prod evil_vector(vals, n);
innocent_function(evil_vector);
}

请注意,这不是编译器,而是依赖于标准库的编译器,这意味着只要您将libstdc++与clang一起使用,它就可以与clang配合使用。但这不符合要求,因此您必须尽快修复innocent_function:https://godbolt.org/z/Tfcn7rdKq

问题是std::vector不是像std::string_viewstd::span那样的引用类。std::vector拥有托管内存。它分配内存并释放所拥有的内存。它不是为了获取外部缓冲区并释放托管缓冲区而设计的。

你能做的是一个非常肮脏的黑客。您可以使用与std::vector完全相同的布局创建新结构,用从外部lib获得的内容分配数据和大小字段,然后使用reinterpret_cast将此结构作为std::vector const&传递。它可以工作,因为您的库不修改向量(我假设他们不会在std::vector const&上执行const_cast)。

缺点是代码无法维护。如果std::vector的布局发生变化,下一次STL更新可能会导致应用程序崩溃。

下面是一个伪代码

struct FakeVector
{
std::byte* Data;
std::size Size;
std::size Capacity;
}; 
void onNewData(std::byte* ptr, size_t size)
{
auto vectorRef = FakeVector{ptr, size, size};
doSomething(*reinterpret_cast<std::vector<std::byte>*>(&vectorRef)); 
}

好吧,我已经找到了适合我的方法。我必须承认,它并不完全符合标准,因为向量的强制转换会导致未定义的行为,但在可预见的未来,我预计这不会失败。想法是使用我自己的Allocator来处理向量,该向量接受遗留代码中的缓冲区并对其进行处理。问题是std::vector<std::byte>在resize()上调用默认初始化,该初始化将缓冲区归零。如果有一种方法可以禁用它,这将是一个完美的解决方案,但我还没有找到。。。因此,丑陋的强制转换来自std::vector<InnerType>,其中InnerType只不过是std::byte,默认构造函数被禁用,而库期望的是std::vector<std::byte>。工作代码显示在https://godbolt.org/z/7jME79EE9,也在这里:

#include <cstdlib>
#include <iostream>
#include <vector>
#include <cstddef>
struct InnerType {
std::byte value;
InnerType() {}
InnerType(std::byte v) : value(v) {}
};
static_assert(sizeof(InnerType) == sizeof(std::byte));
template <class T> class AllocatorExternalBufferT {
T* const _buffer;
const size_t _size;
public:
typedef T value_type;
constexpr AllocatorExternalBufferT() = delete;

constexpr AllocatorExternalBufferT(T* buf, size_t size) : _buffer(buf), _size(size) {}
[[nodiscard]] T* allocate(std::size_t n) {
if (n > _size / sizeof(T)) {
throw std::bad_array_new_length();
}
return _buffer;
}
void deallocate(T*, std::size_t) noexcept {}
};
template <class T, class U> bool operator==(const AllocatorExternalBufferT <T>&, const AllocatorExternalBufferT <U>&) { return true; }
template <class T, class U> bool operator!=(const AllocatorExternalBufferT <T>&, const AllocatorExternalBufferT <U>&) { return false; }
typedef std::vector<InnerType, AllocatorExternalBufferT<InnerType>> BufferDataVector;
typedef std::vector<std::byte, AllocatorExternalBufferT<std::byte>> InterfaceVector;
static void report(const InterfaceVector& vec) {
std::cout << "size=" << vec.size()  << " capacity=" << vec.capacity() << " ";
for(const auto& el : vec) {
std::cout << static_cast<int>(el) << " ";
}
std::cout << "n";
}
int main() {
InnerType buffer4allocator[16] ;
BufferDataVector v((AllocatorExternalBufferT<InnerType>(buffer4allocator, sizeof(buffer4allocator)))); // double parenthesis here for "most vexing parse" nonsense
v.resize(sizeof(buffer4allocator));
std::cout << "memory area kept intact after resizing vector:n";
report(*reinterpret_cast<InterfaceVector*>(&v));    
}

是的,你可以这样做。这不是一种安全的方式,但肯定是可能的。

您所需要做的就是创建一个与std::vector具有相同ABI(内存布局)的伪std::vector。然后将它的内部指针设置为指向您的数据,并将reinterpet_cast您的伪vector设置为std::vector

除非您真的需要,否则我不会推荐它,因为每当编译器更改其std::vectorABI(基本上是字段布局)时,它都会崩溃。尽管公平地说,现在这种情况不太可能发生。

最新更新