[英]Efficient way to return a std::vector in c++
在 function 中返回std::vector
時復制了多少數據,以及將 std::vector 放在自由存儲區(在堆上)並返回指針而不是返回指針的優化程度,即:
std::vector *f()
{
std::vector *result = new std::vector();
/*
Insert elements into result
*/
return result;
}
比:
std::vector f()
{
std::vector result;
/*
Insert elements into result
*/
return result;
}
?
在 C++11 中,這是首選方式:
std::vector<X> f();
即按值返回。
在 C++11 中, std::vector
具有移動語義,這意味着函數中聲明的局部向量將在返回時移動,在某些情況下,編譯器甚至可以忽略移動。
您應該按值返回。
該標准具有提高按值返回效率的特定功能。 它被稱為“復制省略”,在這種情況下更具體地說是“命名返回值優化(NRVO)”。
編譯器沒有實現它,但隨后又編譯器不必須實現內聯函數(或執行任何優化)。 但是,如果編譯器不優化,標准庫的性能可能會很差,並且所有嚴肅的編譯器都實現了內聯和 NRVO(以及其他優化)。
應用NRVO時,以下代碼不會有復制:
std::vector<int> f() {
std::vector<int> result;
... populate the vector ...
return result;
}
std::vector<int> myvec = f();
但用戶可能想要這樣做:
std::vector<int> myvec;
... some time later ...
myvec = f();
復制省略不會阻止這里的復制,因為它是賦值而不是初始化。 但是,您仍然應該按值返回。 在 C++11 中,賦值由不同的東西優化,稱為“移動語義”。 在 C++03 中,上面的代碼確實會導致復制,雖然理論上優化器可以避免它,但實際上它太難了。 因此,在 C++03 中,您應該這樣寫,而不是myvec = f()
:
std::vector<int> myvec;
... some time later ...
f().swap(myvec);
還有另一種選擇,即為用戶提供更靈活的界面:
template <typename OutputIterator> void f(OutputIterator it) {
... write elements to the iterator like this ...
*it++ = 0;
*it++ = 1;
}
然后,您還可以在此基礎上支持現有的基於矢量的接口:
std::vector<int> f() {
std::vector<int> result;
f(std::back_inserter(result));
return result;
}
如果您現有的代碼以比預先固定數量更復雜的方式使用reserve()
,這可能比您現有的代碼效率低。 但是,如果您現有的代碼基本上反復調用向量上的push_back
,那么這個基於模板的代碼應該也一樣好。
是時候發布關於RVO的答案了,我也是......
如果您按值返回一個對象,編譯器通常會優化它,因此它不會被構造兩次,因為在函數中構造它作為臨時對象然后復制它是多余的。 這稱為返回值優化:創建的對象將被移動而不是被復制。
如果編譯器支持命名返回值優化 ( http://msdn.microsoft.com/en-us/library/ms364057(v=vs.80).aspx ),則可以直接返回向量,前提是沒有:
NRVO 優化了冗余的復制構造函數和析構函數調用,從而提高了整體性能。
在您的示例中應該沒有真正的差異。
一個常見的 C++11 之前的習慣用法是傳遞對正在填充的對象的引用。
那么就沒有向量的復制。
void f( std::vector & result )
{
/*
Insert elements into result
*/
}
vector<string> getseq(char * db_file)
如果你想在 main() 上打印它,你應該在循環中進行。
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;
}
}
是的,按價值回報。 編譯器可以自動處理它。
以下代碼將在沒有復制構造函數的情況下工作:
你的日常:
std::vector<unsigned char> foo()
{
std::vector<unsigned char> v;
v.resize(16, 0);
return std::move(v); // move the vector
}
之后,您可以使用 foo 例程獲取向量而不復制自身:
std::vector<unsigned char>&& moved_v(foo()); // use move constructor
結果:moved_v 大小為 16,並由 [0] 填充
就像“按值返回”一樣好,它是一種可能導致錯誤的代碼。 考慮以下程序:
#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;
};
即使使用 GNU g++ 報告選項 -Wall -Wextra -Weffc++,上述錯誤程序也將指示沒有錯誤
如果你必須產生一個值,那么下面的方法可以代替調用 vecFunc() 兩次:
std::vector<std::string> lclvec(vecFunc());
for(std::vector<std::string>::iterator idx=lclvec.begin(); (idx!=lclvec.end()); ++idx)...
以上在循環迭代期間也不會產生匿名對象,但需要一個可能的復制操作(正如某些人所說,在某些情況下可能會被優化掉。但引用方法保證不會產生任何副本。相信編譯器會執行 RVO 不能替代嘗試構建最有效的代碼。如果您可以提出編譯器執行 RVO 的需要,那么您就領先了。
vector<string> func1() const
{
vector<string> parts;
return vector<string>(parts.begin(),parts.end()) ;
}
這在 c++11 之后仍然有效,因為編譯器自動使用移動而不是復制。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.