拒绝std::vector删除其数据

Deny std::vector from deleting its data

本文关键字:数据 删除 vector std 拒绝      更新时间:2023-10-16

我有以下情况:

T* get_somthing(){
    std::vector<T> vec; //T is trivally-copyable
    //fill vec
    T* temp = new T[vec.size()];
    memcpy(temp, vec.data(), vec.size() * sizeof(T));
    return temp;
}

我想通过直接返回std::vector::data来摆脱复制过程,如下所示:

T* get_somthing(){
    std::vector<T> vec; //T is trivally-copyable
    //fill vec
    return temp.data();
}

然而,这是错误的,因为当调用vec析构函数时,数据将被删除。

那么,如何防止vec删除其数据呢?换句话说,我想要某种从std::vector到C++原始动态数组的移动。

附言:改变设计不是一种选择。使用std::vector是强制性的。将pointer返回到array也是强制性的。Becauese它是两个模块之间的包装器。一个需要矢量,另一个需要指针。

附言:改变设计不是一种选择。使用std::vector是强制性的。返回指向数组的指针也是必需的。

更改设计是您的最佳选择。我建议重新考虑这一立场。

(目前)没有办法"窃取"向量的缓冲区,因此考虑到问题中所述的(愚蠢的x20†)限制,复制是可行的。

†Tomasz Leowski将一项提案联系起来,如果将其纳入未来标准,该提案将改变这一点:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4359.pdf(编辑:如前所述,它被c++17拒绝)

††在根据具体要求进行证明之前是愚蠢的。


它是两个模块之间的包装器。一个需要矢量,另一个需要指针。

据推测,另一个需要指针的接口将缓冲区的销毁委托给调用者,可能会使用某种回调,如void delete_somthing(T*)。在我看来,不归还所有权是一个非常糟糕的设计。

如果你确实控制了销毁,你可以将矢量存储在地图中,并在指针被传递进行销毁时擦除矢量:

std::unordered_map<T*, std::vector<T>> storage;
T* get_somthing(){
    std::vector<T> vec; //T is trivally-copyable
    //fill vec
    T* ptr = vec.data();
    storage[ptr] = std::move(vec);
    return ptr;
}
void delete_somthing(T* ptr){
    storage.erase(ptr);
}

在C++11中,没有从矢量中释放缓冲区的选项。

对标准的这种扩展是针对C++17提出的:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4359.pdf但是,正如T.C.所指出的,它被拒绝了:https://issues.isocpp.org/show_bug.cgi?id=81

所以在标准上没有运气。此外,还发布了一个相关问题,该问题已经得到回答,并解释了相同的问题:在不释放内存的情况下销毁std::vector

如果你能够处理这两个库中的任何一个,你可以尝试自定义分配器或其他奇怪的东西(比如将自己绑定到库的内部实现并处理私有向量数据),但实际上,不要

下面是如何使用自定义分配器的示例代码。这假设您可以使vector实际使用自定义分配器。分配器还使用静态变量来控制内部缓冲区的破坏。我已经在VS2015下进行了检查,它的实现只为内部缓冲区调用~vector中的deallocate,即它不使用此分配器管理任何其他分配。

这是一次黑客攻击,我不确定使用它可能会产生什么后果。它肯定不是线程安全的(但在使allow_dealloc线程成为本地线程后可以很容易地修复)

http://coliru.stacked-crooked.com/a/5d969a6934d88064

#include <limits>
#include <vector>
#include <iostream>
template <class T>
class my_alloc {
  std::allocator<T> alloc;
public:
  static bool allow_dealloc;
  typedef T        value_type;
  typedef T*       pointer;
  typedef const T* const_pointer;
  typedef T&       reference;
  typedef const T& const_reference;
  typedef std::size_t    size_type;
  typedef std::ptrdiff_t difference_type;
  pointer allocate(size_type num, const void* = 0) { return alloc.allocate(num);  }
  void deallocate(pointer p, size_type num) { 
      if (allow_dealloc) 
        alloc.deallocate(p, num*sizeof(T));  }

  // Squashed as less important
  template <class U> struct rebind { typedef my_alloc<U> other; };
  pointer address(reference value) const { return &value; }
  const_pointer address(const_reference value) const { return &value; }
  my_alloc() throw() { }
  my_alloc(const my_alloc&) throw() { }
  template <class U> my_alloc(const my_alloc<U>&) throw() { }
  ~my_alloc() throw() { }
  size_type max_size() const throw() { return (std::numeric_limits<size_t>::max)() / sizeof(T); }  
  void construct(pointer p, const T& value) { alloc.construct(p, value); }
  void destroy(pointer p) { p->~T(); }  
};
template <typename T>
bool my_alloc<T>::allow_dealloc = true;
int main()
{
  int* data = 0;
  size_t size = 0;
  {
    my_alloc<int>::allow_dealloc = true;      
    std::vector<int, my_alloc<int>> vec= { 0, 1, 2, 3 };
    vec.push_back(4);
    vec.push_back(5);
    vec.push_back(6);
    my_alloc<int>::allow_dealloc = false;
    data = vec.data();
    size = vec.size();
  }
  for (size_t n = 0; n < size; ++n)
    std::cout << data[n] << "n";
  my_alloc<int> alloc; 
  alloc.deallocate(data, size);
}

如果有使用智能指针的选项,我建议使用别名为的std::shared_ptr

template<typename T>
std::shared_ptr<T> get_somthing(){
    using Vector = std::vector<T>;
    using ReturnT = std::shared_ptr<T>;
    std::vector<T>* vec = new std::vector<T>;
    //fill vec
    std::shared_ptr<Vector> vectorPtr(vec); // (1)
    std::shared_ptr<T> aliasedPtr(vectorPtr, vec->data()); // (2)
    return aliasedPtr;
}

(1) 将创建一个指向要别名的向量的共享指针(2) 创建一个共享指针,该指针将销毁别名shared_ptr,而不是删除包含的数据

我不知道你是否会喜欢这个非常黑客的解决方案,我肯定不会在生产代码中使用它,但请考虑:

#include <iostream>
using namespace std;
#include <vector>
template<class T>
T* get_somthing(){
    std::vector<T> vec = {1,2,3}; //T is trivally-copyable
    static std::vector<T> static_vector = std::move(vec);
    return static_vector.data();
}  
int main() {
    int * is = get_somthing<int>();
    std::cout << is[0] << " " << is[1] << " " << is[2];
    return 0;
}

所以,正如您在get_somthing中看到的,我定义了一个静态向量,与您需要的类型相同,并在它上使用std::move,然后返回它的data()。它实现了你想要的,但这是危险的代码,所以请使用老式的"再次复制数据"方法,让我们等到N4359进入主流编译器。

现场演示:http://ideone.com/3XaSME

编辑:这个想法不起作用,因为没有办法阻止对基类的析构函数的隐式调用(谢谢,molbdnilo)。如果我想起来的话,他们被称为是一件好事。


我不完全确定这是否可行(也很好奇其他人怎么说),但是否有可能从向量继承并覆盖其析构函数(什么都不做)?即使~vector()不是虚拟的(标准中是否有虚拟或非虚拟的要求?)只要您明确使用您的类型,这也应该有效。

通过继承,您将保留所有的好处,特别是内存管理——除了最后一位(您不想要)。

这里的方法不是简单地动态分配向量吗?这就是生命周期与范围无关的资源传统上的管理方式,我认为没有任何理由发明一些非凡的东西。

当然,这个载体应该在一段时间后被销毁;这可能需要将其地址存储在某个地方,作为get_somthing()的副作用,但即使这样,这种策略似乎也比任何其他想法都干净。

你应该做的第一件事就是站起来,去找负责这个设计的人,(以专业的方式口头)打他/她的脸:这是一场混乱

然后,在C++11中有一种方法可以使std::vector具有自动存储持续时间,并且不调用其析构函数:

std::vector放入union

像这样:

template<typename T>
union Ugly {
  std::vector<T> vec;
  Ugly() {
    new (&vec) std::vector<T>(); // Construct
  }
  ~Ugly() {
   // Don't destruct
  }
};
T* get_something(){
  Ugly mess;
  //fill mess.vec
  return mess.vec.data();
}

我不能100%确定这是否仍然算作有效的C++11,但它应该"有效"。现在对不起,我需要洗手,以摆脱为这个代码感到羞耻的哭泣感。。。

哦,还有一件事:你打算如何释放std::vector分配的内存?你知道,你不能(可靠地)使用成员函数data()返回的指针!

好吧,不要在家里尝试,这不好。与其说是实际答案,不如说是一个实验。

(嗯,要求/设计也不好:"玩愚蠢的游戏,赢得愚蠢的奖品")

在你的cpp:

#define private public               // good luck for the code review
#define protected public
#include <vector>                    // (must be the first occurence in the TU)
#undef private                       // do not abuse good things...
#undef protected
template<typename T>
T* my_release(std::vector<T>& v){
    std::vector<T> x;                // x: the local vector with which we mess around
    std::swap(x, v);                 // the given vector is in an OK, empty state now.
    T* out = x._M_impl._M_start;     // first, get the pointer you want
    // x will be destructed at the next '}'. 
    // The dtr only use _M_start and _M_finish, make sure it won't do anything.
    x._M_impl._M_start = nullptr;    
    x._M_impl._M_finish = nullptr;
    // no need to say, the internal state of 'x' is bad, like really bad...
    // also we loose the capacity information, the actual allocator... 
    // -> good luck with memory leaks...
    return out;
}
// usage example
int main(){
    std::vector<int> vi{1,2,3,4,5,6,7,8,9};
    auto n = vi.size();
    int* pi = release(vi);
    for(size_t i=0; i<n; ++i)
        std::cout << pi[i] << ", ";
    return 0;
}

打印1, 2, 3, 4, 5, 6, 7, 8, 9,