Как получить адрес запуска буфера std::vector наиболее элегантно?
Я хочу использовать 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 ответов
Ну, вы можете удалить один набор паренов:
&buffer[0]
но это обычный идиоматический способ сделать это. Если это действительно оскорбляет вас, я полагаю, вы могли бы использовать шаблон - что-то вроде:
template <typename T>
T * StartOf( std::vector <T> & v ) {
return &v[0];
}
Странно, что никто этого не знает!!! в 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);
Спецификация здесь.
Собственно, главная проблема с &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
следует проверять для каждого аргумента функции указателя, это определенно более безопасно.
но это
&(buffer[0])
выглядит некрасиво
Это нормальный способ. Вы можете опустить скобки, хотя:
&buffer[0]
Элегантный способ будет изменить 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 )
как кто-то недавно предложил в качестве редактирования этого ответа, потому что в этом случае вы получите ошибку вывода.
Причина, по которой это выглядит некрасиво, заключается в том, что вы находитесь на грани красивого и чистого кода стиля 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 );
}
Для таких функций я использую служебный класс, 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 делает с итераторами, но избегает заражения шаблонами.
Если вы используете std::vector
только для его свойств RAII (так что он освободит память для вас), и вам на самом деле не нужно его изменять размер или что-то еще, вам может быть лучше использовать Boost scoped_array
boost::scoped_array<TCHAR> buffer( new TCHAR[neededLength] );
callFunction( buffer.get(), neededLength );
scoped_array
позвоню delete[]
на массиве, когда он выходит из области видимости.
Как уже говорилось, нет.
Причина в том, что &buffer[0] - единственный способ, гарантированный стандартом, чтобы получить адрес векторного буфера.