拒绝std :: vector删除其数据

时间:2016-04-15 09:43:12

标签: c++ c++11 vector

我有以下情况:

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 ++ Raw Dynamic Array的某种move-idiiom。

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

9 个答案:

答案 0 :(得分:19)

  

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

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

(目前)没有办法偷走&#34;窃取&#34;一个向量的缓冲区,所以考虑到问题中所述的(愚蠢的††)限制,复制是要走的路。

†Tomasz Lewowski联系了一项提案,如果它包含在未来的标准中,将会改变这一提议: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);
}

答案 1 :(得分:14)

在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

所以标准中没有运气。 此外,相关问题已经发布,已经回答并解释了同样的问题:Destroy std::vector without releasing memory

如果您能够使用其中任何一个库,您可以尝试自定义分配器或其他奇怪的东西(比如将自己绑定到库的内部实现并弄乱私有矢量数据),但实际上,不< / em>的。

答案 2 :(得分:6)

下面是如何使用自定义分配器执行此操作的示例代码。这假设您可以使vector实际使用自定义分配器。此外,allocator使用静态变量来控制内部缓冲区的破坏。我已经在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);
}

答案 3 :(得分:3)

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

#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

答案 4 :(得分:3)

如果有选项可以使用智能指针,我建议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而不是删除包含的数据

答案 5 :(得分:2)

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

<小时/> 我不完全确定这是否可行(并且对其他人所说的很好奇),但是可以继承vector并覆盖它的析构函数(什么也不做)?即使~vector()不是虚拟的(标准中是否有要求是虚拟的?),只要您明确使用您的类型,这应该有效。

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

答案 6 :(得分:2)

Isn't the way to go here simply to allocate the vector dynamically? That's how resources with a life time which is unrelated to scope are traditionally managed, and I don't see any reason to invent something extraordinary.

Of course that vector should be destroyed some time later; that may make it necessary to store its address somewhere as a side effect of get_somthing(), but even then this strategy seems cleaner than any of the other ideas.

答案 7 :(得分:2)

你要做的第一件事就是起床,去负责这个设计的人,(口头上以专业的方式)打他/她的脸:这是一个一团糟。< / p>

然后,在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()返回的指针!

答案 8 :(得分:2)

好的,不要在家里尝试这个,这不好看。它更像是一个实验,而不是一个真正的答案。

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

在你的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,