2013-03-29 32 views
16

Bao nhiêu dữ liệu được sao chép, khi trả về một std :: vector trong một hàm và mức độ tối ưu hóa một cách tối ưu để đặt std :: vector tại cửa hàng miễn phí (trên heap) và trả về một con trỏ thay vì tức là:Cách hiệu quả để trả về một std :: vector trong C++

std::vector *f() 
{ 
    std::vector *result = new std::vector(); 
    /* 
    Insert elements into result 
    */ 
    return result; 
} 

hiệu quả hơn:

std::vector f() 
{ 
    std::vector result; 
    /* 
    Insert elements into result 
    */ 
    return result; 
} 

?

+1

Làm cách nào để truyền véc-tơ bằng tham chiếu và sau đó điền vào bên trong 'f'? –

+2

[RVO] (http://en.wikipedia.org/wiki/Return_value_optimization) là một tối ưu hóa khá cơ bản mà hầu hết các trình biên dịch sẽ có khả năng làm bất cứ lúc nào. –

+0

Khi câu trả lời trôi chảy, nó có thể giúp bạn làm rõ liệu bạn đang sử dụng C++ 03 hoặc C++ 11. Các phương pháp hay nhất giữa hai phiên bản khác nhau khá nhiều. –

Trả lời

24

Trong C++ 11, đây là cách ưa thích:

std::vector<X> f(); 

Đó là, trở lại theo giá trị.

Với C++ 11, std::vector có di chuyển-ngữ nghĩa, có nghĩa là vector địa phương khai báo trong hàm của bạn sẽ được chuyển trên trở lại và trong một số trường hợp thậm chí di chuyển có thể được elided bởi trình biên dịch.

+0

Nó sẽ được di chuyển ngay cả khi không có 'std :: move'? –

+4

@LeonidVolnitsky: Có nếu đó là * local *. Trong thực tế, 'return std :: move (v);' sẽ vô hiệu hóa việc di chuyển-elision ngay cả khi nó có thể chỉ với 'return v;'. Vì vậy, sau này được ưa thích. – Nawaz

0

Thành ngữ phổ biến là chuyển một tham chiếu đến đối tượng đang được lấp đầy.

Sau đó không có sao chép vectơ.

void f(std::vector & result) 
{ 
    /* 
    Insert elements into result 
    */ 
} 
+0

Đó không phải là một thành ngữ trong C++ 11. – Nawaz

+0

@Nawaz Tôi đồng ý. Tôi không chắc những gì thực hành tốt nhất là bây giờ trên SO liên quan đến câu hỏi về C + + nhưng không cụ thể C + + 11. Tôi nghi ngờ tôi nên nghiêng C++ 11 câu trả lời cho một sinh viên, C++ 03 câu trả lời cho một người nào đó eo sâu trong mã sản xuất. Bạn có ý kiến ​​gì? –

+3

Trên thực tế, sau khi phát hành C++ 11 (đó là 19 tháng tuổi), tôi xem xét mọi câu hỏi là C++ 11 câu hỏi, trừ khi nó được tuyên bố rõ ràng là C++ 03 câu hỏi. – Nawaz

1

Đó là thời gian tôi đăng một câu trả lời về RVO, me too ...

Nếu bạn quay trở lại một đối tượng theo giá trị, trình biên dịch thường tối ưu hóa này vì vậy nó không được xây dựng hai lần, vì nó không cần thiết để xây dựng nó trong chức năng tạm thời và sau đó sao chép nó. Điều này được gọi là tối ưu hóa giá trị trả về: đối tượng đã tạo sẽ được di chuyển thay vì được sao chép.

0

Nếu trình biên dịch hỗ trợ Named Return Value Optimization (http://msdn.microsoft.com/en-us/library/ms364057(v=vs.80).aspx), bạn có thể trực tiếp trả lại vector cung cấp mà không có:

  1. con đường khác nhau trở về tên đối tượng khác nhau
  2. Nhiều con đường trở lại (ngay cả khi đối tượng có tên tương tự được trả lại trên tất cả đường dẫn) với trạng thái EH được giới thiệu.
  3. Đối tượng được đặt tên trả lại được tham chiếu trong khối nội tuyến asm.

NRVO tối ưu hóa trình tạo bản sao dự phòng và cuộc gọi hủy và do đó cải thiện hiệu suất tổng thể.

Không nên có sự khác biệt thực sự trong ví dụ của bạn.

16

Bạn nên trả về giá trị.

Tiêu chuẩn có một tính năng cụ thể để cải thiện hiệu quả trả về theo giá trị. Nó được gọi là "sao chép elision", và cụ thể hơn trong trường hợp này là "tối ưu hóa giá trị trả về có tên (NRVO)".

Trình biên dịch không phải thực hiện nó, nhưng sau đó một lần nữa trình biên dịch không để thực hiện chức năng nội tuyến (hoặc thực hiện bất kỳ tối ưu hóa nào cả). Nhưng hiệu suất của các thư viện chuẩn có thể khá kém nếu các trình biên dịch không tối ưu hóa, và tất cả các trình biên dịch nghiêm trọng đều thực hiện nội tuyến và NRVO (và các tối ưu hóa khác).

Khi NRVO được áp dụng, thì sẽ không có sao chép trong đoạn mã sau:

std::vector<int> f() { 
    std::vector<int> result; 
    ... populate the vector ... 
    return result; 
} 

std::vector<int> myvec = f(); 

Nhưng người dùng có thể muốn làm điều này:

std::vector<int> myvec; 
... some time later ... 
myvec = f(); 

Sao chép sự bỏ bớt không ngăn cản một bản sao ở đây bởi vì đó là một bài tập thay vì khởi tạo. Tuy nhiên, bạn nên vẫn còn trả về theo giá trị. Trong C++ 11, nhiệm vụ được tối ưu hóa bởi một cái gì đó khác, được gọi là "di chuyển ngữ nghĩa". Trong C++ 03, đoạn mã trên không gây ra một bản sao, và mặc dù trong lý thuyết một trình tối ưu hóa có thể tránh được nó, trong thực tế nó quá khó. Vì vậy, thay vì myvec = f(), trong C++ 03 bạn nên viết này:

std::vector<int> myvec; 
... some time later ... 
f().swap(myvec); 

Có một tùy chọn, mà là để cung cấp một giao diện linh hoạt hơn cho người sử dụng:

template <typename OutputIterator> void f(OutputIterator it) { 
    ... write elements to the iterator like this ... 
    *it++ = 0; 
    *it++ = 1; 
} 

Bạn có thể sau đó cũng hỗ trợ giao diện vector-based hiện trên đầu trang của rằng:

std::vector<int> f() { 
    std::vector<int> result; 
    f(std::back_inserter(result)); 
    return result; 
} 

này thể thể ít hiệu quả hơn so với mã hiện tại của bạn, nếu mã hiện tại của bạn sử dụng reserve() theo cách phức tạp hơn chỉ là một số tiền cố định ở phía trước. Nhưng nếu mã hiện tại của bạn về cơ bản gọi số push_back trên vectơ nhiều lần, thì mã mẫu dựa trên mẫu này phải là tốt.

+0

+1 cho câu trả lời chi tiết, tốt hơn nhiều so với tôi. – Nawaz

+0

Đã bỏ phiếu cho câu trả lời thực sự và chi tiết nhất. Tuy nhiên, trong phiên bản swap() của bạn (** cho C++ 03 không có NRVO **), bạn vẫn sẽ có một bản copy-constructor được tạo bên trong f(): từ biến _result_ thành một đối tượng tạm thời ẩn sẽ được hoán đổi lần cuối để _myvec_. – JenyaKh

+0

@JenyaKh: chắc chắn, đó là vấn đề về chất lượng thực hiện. Các tiêu chuẩn không yêu cầu C++ 03 triển khai thực hiện NRVO, giống như nó không yêu cầu chức năng nội tuyến. Sự khác biệt từ chức năng nội tuyến, là nội tuyến không thay đổi ngữ nghĩa hoặc chương trình của bạn trong khi NRVO làm. Mã di động phải hoạt động với hoặc không có NRVO. Mã được tối ưu hóa cho một triển khai cụ thể (và các cờ trình biên dịch cụ thể) có thể tìm kiếm các bảo đảm liên quan đến NRVO trong tài liệu của riêng mình. –

1

Tốt như "trả về theo giá trị" có thể, đó là loại mã có thể dẫn đến lỗi. Hãy xem xét các chương trình sau đây:

#include <string> 
    #include <vector> 
    #include <iostream> 
    using namespace std; 
    static std::vector<std::string> strings; 
    std::vector<std::string> vecFunc(void) { return strings; }; 
    int main(int argc, char * argv[]){ 
     // set up the vector of strings to hold however 
     // many strings the user provides on the command line 
     for(int idx=1; (idx<argc); ++idx){ 
     strings.push_back(argv[idx]); 
     } 

     // now, iterate the strings and print them using the vector function 
     // as accessor 
     for(std::vector<std::string>::interator idx=vecFunc().begin(); (idx!=vecFunc().end()); ++idx){ 
     cout << "Addr: " << idx->c_str() << std::endl; 
     cout << "Val: " << *idx << std::endl; 
     } 
    return 0; 
    }; 
  • Q: Điều gì sẽ xảy ra khi ở trên được thực hiện? A: Một coredump.
  • Hỏi: Tại sao trình biên dịch không hiểu sai? A: Bởi vì chương trình là cú pháp, mặc dù không phải ngữ nghĩa, chính xác.
  • Hỏi: Điều gì sẽ xảy ra nếu bạn sửa đổi vecFunc() để trả lại tham chiếu? A: Chương trình chạy để hoàn thành và tạo ra kết quả mong đợi.
  • Q: Sự khác biệt là gì? A: Trình biên dịch không phải tạo và quản lý các đối tượng ẩn danh. Lập trình viên đã hướng dẫn trình biên dịch sử dụng chính xác một đối tượng cho trình vòng lặp và để xác định điểm cuối, thay vì hai đối tượng khác nhau như ví dụ bị hỏng.

Chương trình có sai sót trên sẽ cho thấy không có lỗi thậm chí nếu có ai sử dụng GNU g ++ tùy chọn -Wall -Wextra -WeffC++

báo cáo Nếu bạn phải tạo ra một giá trị, thì sau đây sẽ làm việc ở vị trí của gọi vecFunc() hai lần:

std::vector<std::string> lclvec(vecFunc()); 
    for(std::vector<std::string>::iterator idx=lclvec.begin(); (idx!=lclvec.end()); ++idx)... 

trên đây cũng sản xuất không có đối tượng ẩn danh trong lần lặp của vòng lặp, nhưng đòi hỏi một hoạt động sao chép có thể (trong đó, như một số lưu ý, có thể được tối ưu hóa đi dưới một số trường hợp nhưng đảm bảo phương pháp tham khảo. rằng sẽ không có bản sao nào được sản xuất. ompiler sẽ thực hiện RVO không thay thế cho việc cố gắng xây dựng mã hiệu quả nhất mà bạn có thể.Nếu bạn có thể tranh luận sự cần thiết cho trình biên dịch để làm RVO, bạn đang đi trước của trò chơi.

0
vector<string> getseq(char * db_file) 

Và nếu bạn muốn in trên chính() bạn nên làm điều đó trong một vòng lặp.

int main() { 
    vector<string> str_vec = getseq(argv[1]); 
    for(vector<string>::iterator it = str_vec.begin(); it != str_vec.end(); it++) { 
     cout << *it << endl; 
    } 
} 
0

Có, trả về theo giá trị. Trình biên dịch có thể xử lý nó tự động.

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