Как получить адрес запуска буфера std :: vector наиболее элегантно? - PullRequest
12 голосов
/ 27 августа 2009

Я хочу использовать 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() );

Код выше работает, но этот &(buffer[0]) выглядит безобразно. Есть ли более элегантный способ добиться того же?

Ответы [ 11 ]

23 голосов
/ 15 января 2014

Странно, что никто этого не знает !!! в 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);

Спецификация здесь .

19 голосов
/ 27 августа 2009

Ну, вы можете удалить один набор паренов:

&buffer[0]

но это обычный идиоматичный способ сделать это. Если это действительно вас оскорбляет, я полагаю, вы могли бы использовать шаблон, например:

template <typename T> 
T * StartOf( std::vector <T> & v ) {
    return &v[0];
}
19 голосов
/ 27 августа 2009

На самом деле, главная проблема с &buffer[0] (обратите внимание на отсутствие парантезов) не в том, что это не очень красиво. (В любом случае, это субъективно. Я помню, что buffer.begin(), buffer.end() совсем не было симпатичным, когда я впервые научился использовать STL.)

Основная проблема заключается в том, что он вызывает неопределенное поведение всякий раз, когда 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) красивее &buffer[0], остается на ваше усмотрение. Однако, учитывая, что NULL должен проверяться для каждого аргумента функции указателя, это определенно более безопасно.

12 голосов
/ 27 августа 2009

но это &(buffer[0]) выглядит некрасиво

Это нормальный способ. Вы можете опустить скобки, хотя:

&buffer[0]
7 голосов
/ 27 августа 2009

Нет.

4 голосов
/ 27 августа 2009

Попробуйте &(buffer.front()), но это не намного красивее:)

3 голосов
/ 27 августа 2009

Элегантным способом было бы изменить 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<sometype>), и вы все равно сможете писать callFunction( buffer.begin(), buffer.end() );.

Обратите внимание, что вы не можете объявить шаблонную функцию как void callFunction( typename std::vector<typename T::value_type>::iterator begin_it, typename std::vector<typename T::value_type>::iterator end_it ), как кто-то недавно предложил в качестве редактирования этого ответа, потому что в этом случае вы получите ошибку вывода.

2 голосов
/ 27 августа 2009

Для подобных функций я использую служебный класс SizedPtr<T>, который в основном содержит указатель и количество элементов. Набор функций преобразователя создает SizedPtr<T> из разных входов. Таким образом, вызов меняется на:

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

Можно даже добавить неявный std::vector конструктор к SizedPtr, но я хотел избежать этой зависимости.

Это помогает, только если 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 с итераторами, но избегает заражения шаблонами.

2 голосов
/ 27 августа 2009

Причина, по которой это выглядит некрасиво, заключается в том, что вы находитесь на грани красивого и чистого кода стиля 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 );
}
1 голос
/ 13 августа 2010

Если вы используете std::vector только для его свойств RAII (чтобы освободить память для вас), и вам на самом деле не нужно его изменять размер или что-то еще, вам может быть лучше использовать Boost scoped_array

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

scoped_array вызовет delete[] для массива, когда он выйдет из области видимости.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...