2009-08-27 25 views
10

Tôi muốn sử dụng std :: vector để phân bổ bộ nhớ động. Kịch bản là:Làm thế nào để có được địa chỉ của std :: vector buffer bắt đầu một cách tao nhã nhất?

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()); 

Đoạn mã trên hoạt động, nhưng điều này có vẻ xấu xí. Có cách nào thanh lịch hơn để đạt được điều tương tự không?

+12

Bạn nên đổi tên đệm để elegantBuffer ... :) –

+0

Can Bạn không nhận được callFunction để chấp nhận một vector? điều này dường như với tôi là cách thanh lịch thực sự. Nếu không, bạn đang bị mắc kẹt trong vùng đất không có đất trống giữa C và C++. –

+0

@bismuth: Tôi biết nó sẽ là một giải pháp tốt đẹp để thay đổi chức năng của người tiêu dùng để chấp nhận vectơ, nhưng nó không phải là một lựa chọn. Trong thực tế, tôi chỉ sử dụng các vector như là một phân bổ một lần kích thước chưa biết trước. – sharptooth

Trả lời

15

Vâng, bạn có thể loại bỏ một bộ Parens:

&buffer[0] 

nhưng đó là cách thành ngữ phổ biến để làm việc đó. Nếu nó thực sự xúc phạm đến bạn, tôi nghĩ bạn có thể sử dụng một mẫu - một cái gì đó như:

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

Khi bạn làm điều này trong một hàm, bạn cũng có thể sử dụng nó để kiểm tra xem vùng chứa có trống không. – sbi

+0

Tôi không đồng ý. Trong loại mã mà tôi viết có thể sử dụng chức năng này (tôi không sử dụng nó trong thực tế) kiểm tra sẽ không cần thiết –

+2

Nó có thể khác với bạn, nhưng tôi đã làm việc trong thập kỷ qua, tôi ' d đặt mã như thế này vào hộp công cụ của mọi người. Và vì tôi có rất ít quyền kiểm soát những gì mọi người khác đang làm, tôi sẽ đặt ít nhất một kiểm tra gỡ lỗi vào điều này bất kể nhu cầu của tôi là gì. – sbi

4

Hãy thử &(buffer.front()), nhưng nó không phải là đẹp hơn nhiều :)

+0

Điều này có bất lợi là nó sẽ không hoạt động với các loại mảng đồng bằng. –

+0

Nó không quan trọng, câu hỏi là về 'std :: vector'. Tôi nghĩ thực tế rằng nó không đẹp hơn nhiều nữa ;-) –

12

nhưng &(buffer[0]) này trông xấu xí

Đó là cách bình thường. Bạn có thể bỏ qua các dấu ngoặc đơn, mặc dù:

&buffer[0] 
6

số

+1

+1 Brevity là linh hồn của trí thông minh! –

+2

Tóm tắt lịch phát sóng không có gì nếu nó truyền tải một câu trả lời sai. Cả Neil và tôi đã đăng các giải pháp đơn giản hơn (với mức giảm đáng kể 17% nhân vật). –

+0

"Ít thao tác gõ phím" không nhất thiết phải tương đương với "đơn giản hơn" hoặc "thanh lịch hơn" đối với tôi. "Clarity" đi vào hoạt động, đặc biệt là trong một ngôn ngữ như C++, nơi những thay đổi cú pháp nhỏ có thể có những hậu quả đáng ngạc nhiên. Tại sao làm cho người đọc dừng lại và gãi đầu của họ trên ưu tiên nhà điều hành, khi một tập hợp thêm các parens có thể làm cho ý định tinh thể rõ ràng? Tôi vẫn còn với Pavel trên cái này! –

3

cách thanh lịch sẽ được thay đổi callFunction hay để viết wrapper cho nó như sau:

void callFunction(std::vector<TCHAR>::iterator begin_it, std::vector<TCHAR>::iterator end_it) 
{ 
    callFunction(&*begin_it, std::distance(begin_it, end_it)); 
} 

int neededLength = computeLength(); 
std::vector<TCHAR> buffer(neededLength); 
callFunction(buffer.begin(), buffer.end()); 

Bạn có thể thậm chí làm wrapper cho tất cả các chức năng như vậy (với các loại khác nhau, không chỉ 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)); 
} 

loại T sẽ được suy luận đúng và bạn sẽ có thể vẫn viết callFunction(buffer.begin(), buffer.end());

+0

Nhưng điều này chỉ bao gồm các trường hợp khi các container/chuỗi là điều duy nhất để được thông qua chức năng. Chắc chắn, bạn có thể mở rộng điều này để thêm các đối số bổ sung tùy ý, nhưng sự lộn xộn của mẫu kết quả là không hề đẹp - và sự đẹp đẽ là một mục tiêu. (Và phiên bản dùng các trình vòng lặp nên - ít nhất là trong một chú thích - chỉ rõ rằng nó cần các trình vòng lặp chuyển tiếp ít nhất.) – sbi

+0

Chỉ mất * chỉ * trình lặp của std :: vector. Chúng là các trình vòng lặp chuyển tiếp. Không cần thêm ý kiến. –

+0

@Kirill: Về các trình vòng lặp bạn nói đúng. Đó là một bộ não của tôi. Lấy làm tiếc. – sbi

1

Như đã nói, không.

Lý do là & bộ đệm [0] là cách duy nhất được bảo đảm bởi tiêu chuẩn để có được sự thích nghi của bộ đệm vector.

2

Lý do có vẻ xấu xí là vì bạn đang ở đường biên giới của mã phong cách C++ đẹp và sạch sẽ và mã kiểu C đẹp và sạch. Mã C++ sử dụng các trình vòng lặp, mã C sử dụng các con trỏ và các kích cỡ.

Bạn có thể tạo ra một số keo để phá vỡ những vấn đề này:

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

và gọi nó trong mã khách hàng.

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); 
} 
2

Đối với chức năng như thế này, tôi sử dụng lớp con trỏ và số phần tử cơ bản. Một tập hợp các chức năng chuyển đổi tạo ra các SizedPtr<T> từ các đầu vào khác nhau. Vì vậy, các cuộc gọi thay đổi:

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

Một thậm chí có thể thêm một ngầm std::vector constructor để SizedPtr, nhưng tôi muốn tránh sự phụ thuộc này.

Điều này chỉ giúp nếu callFunction nằm trong tầm kiểm soát của bạn. Thật vui khi được làm việc với nếu bạn làm việc với các loại vectơ khác nhau trong một ứng dụng và bạn muốn hợp nhất. Nếu bạn thường làm việc với std::vector, thì hầu như vô nghĩa.

Rougly:

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, ... 

} 

Ý tưởng đằng sau là để tách các hoạt động - thao tác một chuỗi liền kề của các yếu tố - từ việc lưu trữ (std :: vector). Nó tương tự như những gì STL làm với các trình vòng lặp, nhưng tránh nhiễm mẫu.

18

Thực ra, vấn đề chính với &buffer[0] (lưu ý sự vắng mặt của các dấu ngoặc đơn) không phải là nó không thực sự đẹp. (Đó là chủ quan anyway. Tôi nhớ tìm thấy buffer.begin(), buffer.end() không đẹp chút nào, khi lần đầu tiên tôi học cách sử dụng STL.)

Vấn đề chính là nó gọi hành vi không xác định bất cứ khi nào buffer trống - và hầu hết mã không bao giờ kiểm tra . Đó là lý do tôi đặt chúng thành hộp công cụ của tôi:

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());} 

Sử dụng này, bạn có thể viết mã của bạn như

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

begin_ptr(buffer) là đẹp hơn &buffer[0] còn lại để bạn có thể quyết định. Tuy nhiên, cho rằng NULL nên được kiểm tra cho mỗi đối số hàm con trỏ, nó chắc chắn là an toàn hơn.

+0

+1 cho tất cả, ngoại trừ C-ism 'NULL'. –

+2

@mmutz: Tôi không mạnh mẽ chuyên nghiệp hoặc contra 'NULL' /' 0'. Đó là chủ yếu là một câu hỏi về phong cách (mặc dù 'NULL' là tốt hơn để grep cho). Trong thực tế, trong mã ban đầu tôi đã sử dụng '0'. Tôi chỉ thay đổi điều này khi tôi sao chép mã vào bài đăng, vì tôi nghĩ rằng tôi thấy nhiều 'NULL' ở đây hơn' 0'. : o> – sbi

+0

http://www.research.att.com/~bs/bs_faq2.html#null –

1

Nếu bạn đang sử dụng std::vector chỉ dành riêng cho các thuộc tính của nó RAII (để nó sẽ giải phóng bộ nhớ cho bạn), và bạn không thực sự cần nó để thay đổi kích thước hoặc bất cứ điều gì, bạn có thể được tốt hơn bằng cách sử dụng Boost scoped_array

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

scoped_array sẽ gọi delete[] trên mảng khi nó nằm ngoài phạm vi.

17

Thật kỳ lạ khi không ai biết điều này !!! trong C++ 11 bạn có thể sử dụng:

buffer.data() 

nó có thể nhận được địa chỉ của vector Tôi có thử nghiệm nó:

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); 
+0

... chỉ trong C++ 11. –

+0

kích thước bộ đệm có thể nhỏ hơn 10 byte, nhưng bạn sao chép 10 byte, có thể xảy ra sự cố – kchkg

Các vấn đề liên quan