【问题标题】:How to get the address of the std::vector buffer start most elegantly?如何最优雅地获取 std::vector 缓冲区的地址?
【发布时间】:2009-08-27 07:45:19
【问题描述】:

我想使用 std::vector 动态分配内存。场景是:

int neededLength = computeLength(); // some logic here

// this will allocate the buffer     
std::vector<TCHAR> buffer( neededLength );

// call a function that accepts TCHAR* and the number of elements
callFunction( &(buffer[0]), buffer.size() );

上面的代码有效,但是这个&amp;(buffer[0]) 看起来很难看。有没有更优雅的方法来达到同样的效果?

【问题讨论】:

  • 你应该将缓冲区重命名为优雅缓冲区... :)
  • 你不能让 callFunction 接受向量吗?在我看来,这似乎是真正优雅的方式。否则你会被困在 C 和 C++ 之间不雅的无人区。
  • @bismuth:我知道将消费者函数更改为接受向量是一个不错的解决方案,但这不是一个选项。事实上,我只将向量用作预先分配大小未知的数组。
  • 如果您使用它的目的只是分配一次,您可能会牺牲范围自动删除的便利性并使用 TCHAR* buffer = new TCHAR[neededLength];只要记住在完成后删除 [] 缓冲区即可。
  • @cheshirekow:记住是这里的关键问题。如果抛出异常delete[] 将不会运行,所以我必须使用单独的try-catch 来处理delete 缓冲区。这就是为什么vector 或类似的东西更可取的原因。

标签: c++ stl vector


【解决方案1】:

没有人知道这真的很奇怪! 在 C++11 中你可以使用:

buffer.data()

它可以得到向量的地址 我已经测试过了:

vector<char>buffer;
buffer.push_back('w');
buffer.push_back('h');
buffer.push_back('a');
buffer.push_back('t');
buffer.push_back('\0');
char buf2[10];
memcpy(buf2,buffer.data(),10);

规范here

【讨论】:

  • 缓冲区大小可能小于 10 个字节,但你复制了 10 个字节,可能会崩溃
  • 请注意,该问题是在 C++11 创建前 3 年发布的
【解决方案2】:

好吧,你可以删除一组括号:

&buffer[0]

但这是常见的惯用方式。如果它真的冒犯了你,我想你可以使用一个模板——比如:

template <typename T> 
T * StartOf( std::vector <T> & v ) {
    return &v[0];
}

【讨论】:

  • 一旦你在一个函数中这样做了,你不妨用它来检查容器是否为空。
  • 我不同意。在我编写的可以使用此功能的代码中(实际上我不使用它),不需要检查
  • 对你来说可能会有所不同,但我在过去十年左右工作的地方,我会将这样的代码放入每个人的工具箱中。而且由于我几乎无法控制其他人在做什么,因此无论我自己的需要是什么,我都会至少对此进行调试检查。
  • 嗯,C++ 的哲学一直是“不要为不使用的东西付费”,这是我非常同意的哲学。当然,您无法控制人们如何使用您的 API,但这是他们的问题,而不是您的问题。
  • 对我来说似乎是“断言”的完美用途。
【解决方案3】:

实际上,&amp;buffer[0] 的主要问题(注意没有括号)并不是它真的不漂亮。 (无论如何这都是主观的。我记得当我第一次学习使用 STL 时,我发现 buffer.begin(), buffer.end() 一点也不漂亮。)

主要问题是,只要buffer 为空,它就会调用未定义的行为——而且大多数代码从不检查这一点。这就是我将这些放入工具箱的原因:

template <class T, class TAl>
inline T* begin_ptr(std::vector<T,TAl>& v)
{return  v.empty() ? NULL : &v[0];}

template <class T, class TAl>
inline const T* begin_ptr(const std::vector<T,TAl>& v)
{return  v.empty() ? NULL : &v[0];}

template <class T, class TAl>
inline T* end_ptr(std::vector<T,TAl>& v)
{return v.empty() ? NULL : (begin_ptr(v) + v.size());} 

template <class T, class TAl>
inline const T* end_ptr(const std::vector<T,TAl>& v)
{return v.empty() ? NULL : (begin_ptr(v) + v.size());}

使用这些,您可以将代码编写为

callFunction( begin_ptr(buffer), buffer.size() );

begin_ptr(buffer) 是否比&amp;buffer[0] 漂亮由您决定。然而,考虑到NULL 应该检查每个指针函数参数,它肯定更安全。

【讨论】:

  • @mmutz:我不是强烈赞成或反对NULL/0。这主要是一个风格问题(尽管NULL 更适合grep)。事实上,在原始代码中我使用了0。我只是在将代码复制到帖子中时才更改了这一点,因为我认为我在这里看到的 NULL0 更多。 :o>
  • @mmutz:我知道nullptr。我想我会尽快切换到那个。
【解决方案4】:

但是这个&amp;(buffer[0]) 看起来很丑

这是正常的方式。不过,您可以省略括号:

&buffer[0]

【讨论】:

    【解决方案5】:

    没有。

    【讨论】:

    • 如果传达错误的答案,简洁将无济于事。 Neil 和我都发布了更简单的解决方案(字符数减少了 17%)。
    • “更少的击键”对我来说并不一定等同于“更简单”或“更优雅”。 “清晰”开始发挥作用,特别是在像 C++ 这样的语言中,微小的句法变化可能会产生令人惊讶的后果。当额外的一组括号可以使意图一清二楚时,为什么要让读者停下来并为运算符优先级而挠头呢?我仍然和帕维尔一起做这个!
    • 通过将缓冲区重命名为 b,您可以获得更少的字符数!
    • @Jim:括号在这里有什么帮助?他们没有,或者更确切地说,他们不应该:如果你在这个简单的情况下不清楚优先级,那就是个问题。括号在复杂的表达式中非常有用,但不适用于这些简单的情况,在这些情况下,作为 C++ 程序员,您必须知道一个简单、明确的规则。是的,简洁不等于清晰,但正如 Pavel 本人所证明的那样,它可以大大有助于实现它。
    • 说到优雅——Kirill [proposed][1] 确实提出了更好的解决方案。 [1]:stackoverflow.com/questions/1339470/…
    【解决方案6】:

    试试&amp;(buffer.front()),但不是更漂亮:)

    【讨论】:

    • 它的缺点是它不适用于普通数组类型。
    • 没关系,问题是关于std::vector。我认为它不漂亮的事实要糟糕得多;-)
    【解决方案7】:

    优雅的方法是更改​​callFunction 或为其编写包装器,如下所示:

    // legacy function
    void callFunction( TCHAR* buf, int buf_size)
    {
      // some code
    }
    
    // helpful template
    void callFunction( std::vector<TCHAR>::iterator begin_it, std::vector<TCHAR>::iterator end_it )
    {
      callFunction( &*begin_it, std::distance( begin_it, end_it ) );
    }
    
    // somewhere in the code
    int neededLength = computeLength();
    std::vector<TCHAR> buffer( neededLength );
    callFunction( buffer.begin(), buffer.end() );
    

    您甚至可以为所有此类函数制作包装器(使用不同的类型,而不仅仅是 TCHAR):

    template<typename T>
    void callFunction( T begin_it, typename std::vector<typename T::value_type>::iterator end_it )
    {
      callFunction( &*begin_it, std::distance( begin_it, end_it ) );
    }
    

    类型 T 将被正确推断(如 std::vector&lt;sometype&gt;),您仍然可以编写 callFunction( buffer.begin(), buffer.end() );

    请注意,您不能将模板函数声明为 void callFunction( typename std::vector&lt;typename T::value_type&gt;::iterator begin_it, typename std::vector&lt;typename T::value_type&gt;::iterator end_it ),正如最近有人提议的那样作为对此答案的编辑,因为在这种情况下,您将收到推论错误。

    【讨论】:

    • 但这仅涵盖容器/序列是唯一要传递给函数的内容的情况。当然,您可以扩展它以添加任意附加参数,但生成的模板混乱一点也不漂亮——漂亮是一个目标。 (并且采用迭代器的版本应该 - 至少在评论中 - 指定它至少需要转发迭代器。)
    • 它需要 only 个 std::vector 的迭代器。它们是前向迭代器。无需额外的 cmets。
    • @Kirill:关于迭代器,你是对的。那是我的脑残。对不起。
    【解决方案8】:

    它看起来难看的原因是因为你处于漂亮干净的 C++ 风格代码和漂亮干净的 C 风格代码的边缘。 C++ 代码使用迭代器,C 代码使用指针和大小。

    您可以创建一些胶水来规避这些问题:

    template< typename at_Container, typename at_Function >
    void for_container( at_Container& c, at_Function f ) {
        f( &c[0], c.size() );
    }
    

    并在客户端代码中调用它。

    void afunction( int* p, size_t n ) { 
       for( int* p = ap; p != ap+n; ++p ) {
         printf( "%d ", *p );
       }
    }
    
    void clientcode() {
       std::vector<int> ints(30,3);
       for_container( ints, afunction );
    }
    

    【讨论】:

      【解决方案9】:

      对于这些函数,我使用了一个实用程序类SizedPtr&lt;T&gt;,它基本上包含一个指针和一个元素计数。一组转换器函数从不同的输入创建SizedPtr&lt;T&gt;。所以调用变为:

      vector<TCHAR> foo;
      callFunction(sizedptr(foo));
      

      甚至可以向SizedPtr 添加一个隐式的std::vector 构造函数,但我想避免这种依赖关系。

      这只有在callFunction 受您控制时才有用。如果您在一个应用程序中使用不同的矢量类型并且想要整合,那么与您合作是一种乐趣。如果您通常使用std::vector,那几乎没有意义。

      大致:

      template<typename T>
      class SizedPtr
      {
          T * m_ptr;
          size_t m_size;
        public:
          SizedPtr(T* p, size_t size) : ... {}
          T * ptr() { return m_ptr; }
          size_t size() const { return m_size; }
      
         // index access, STL container interface, Sub-Sequence, ...
      
      }
      

      这背后的想法是将操作(操作连续的元素序列)与存储(std::vector)分开。它类似于 STL 对迭代器所做的事情,但避免了模板感染。

      【讨论】:

        【解决方案10】:

        如前所述,不。

        原因是 &buffer[0] 是标准保证获取向量缓冲区地址的唯一方法。

        【讨论】:

          【解决方案11】:

          如果您使用 std::vector 只是为了它的 RAII 属性(这样它会为您释放内存),并且您实际上不需要它来调整大小或其他任何东西,您最好使用 @987654321 @

          boost::scoped_array<TCHAR> buffer( new TCHAR[neededLength] );
          callFunction( buffer.get(), neededLength );
          

          scoped_array 将在数组超出范围时调用delete[]

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2013-06-09
            • 2012-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-10-21
            • 2013-04-16
            • 2014-12-12
            相关资源
            最近更新 更多