関数から「ベクター」を返すのはなぜですか?


108

このコードを検討してください。このタイプのコードを何度か見ました。wordsローカルベクトルです。関数から返すにはどうすればよいですか?

死なないことを保証できますか?

 std::vector<std::string> read_file(const std::string& path)
 {
    std::ifstream file("E:\\names.txt");

    if (!file.is_open())
    {
        std::cerr << "Unable to open file" << "\n";
        std::exit(-1);
    }

    std::vector<string> words;//this vector will be returned
    std::string token;

    while (std::getline(file, token, ','))
    {
        words.push_back(token);
    }

    return words;
}

18
戻るときにコピーされます。
songyuanyao 14年

6
誰も保証するものでありません。
Maroun、2014年

7
あなたの関数が参照を返す場合にのみ問題があります:std::vector<std::string>&
Caduchon

14
@songyuanyaoいいえ、移動されます。
2014年

15
@songyuanyaoはい。C ++ 11は現在の標準なので、C ++ 11はC ++です。
右折2014年

回答:


68

死なないことを保証できますか?

参照が返されない限り、そのようにしても問題ありません。words結果を受け取る変数に移動されます。

ローカル変数はスコープ外になります。移動(またはコピー)された後。


2
しかし、1000エントリを保持する可能性のあるベクトルについて、効率的またはパフォーマンスに関する懸念がありますか?
2015

@zadaneこれは問題でしたか?また、戻り値のコピーを実際に取得することを回避する方法についても触れました(少なくとも現在の標準で利用可能)。
ῥεῖπάντα

2
問題ではありませんが、私はその観点から独立して答えを探していました。私が質問を投稿するかどうかはわかりませんが、彼らはこれを重複とマークするのではないかと思います:)
zar

@zadane 「私は彼らがこれの複製をマークするのではないかと心配している」かもしれない。ただ、見てい高いが、答えを投票。古い実装でも、心配する必要はありません。いずれにせよ、それらはコンパイラによってほとんど正しく最適化されます。
ῥεῖπάντα

107

C ++ 11より前:

関数はローカル変数ではなく、そのコピーを返します。ただし、コンパイラは、実際のコピーアクションが行われない最適化を実行する場合があります。

この質問と回答を見るを参照してください。

C ++ 11:

関数は値を移動します。詳細については、この回答を参照してください。


2
コピーではなく移動されます。これは保証されています。
2014年

1
これはC ++ 10にも適用されますか?
Tim Meyer

28
C ++ 10のようなものはありません。
14年

C ++ 03には移動のセマンティクスはありませんでしたが(ただし、コピーは省略された可能性があります)、C ++はC ++ 11であり、問​​題はC ++に関するものでした。
右折2014年

19
C ++ 11専用の質問用に別のタグがあります。私たちの多く、特に大企業のプログラマーは、まだC ++ 11を完全にはサポートしていないコンパイラーにまだこだわっています。質問を更新して、両方の標準に対して正確になりました。
Tim Meyer

26

私はあなたがC(およびC ++)で関数から配列を返すことが許可されていない(または少なくとも期待どおりに機能しない)問題を参照していると思います-これは配列が返されるためです(あなたがそれを書いた場合)単純な形式)は、スタック上の実際の配列へのポインターを返し、関数が戻るとすぐに削除されます。

しかし、この場合は機能します。これstd::vectorは、クラスであり、構造体と同様に、クラスを呼び出し元のコンテキストにコピーできるためです。[実際、ほとんどのコンパイラは「戻り値の最適化」と呼ばれるものを使用してこの特定のタイプのコピーを最適化します。特に、関数から返されたときにラージオブジェクトがコピーされないようにするために導入されましたが、これは最適化であり、プログラマの観点からは、オブジェクトに対して割り当てコンストラクタが呼び出されたかのように動作します]

返される関数内にあるものへのポインターまたは参照を返さない限り、問題ありません。


13

動作をよく理解するには、次のコードを実行します。

#include <iostream>

class MyClass
{
  public:
    MyClass() { std::cout << "run constructor MyClass::MyClass()" << std::endl; }
    ~MyClass() { std::cout << "run destructor MyClass::~MyClass()" << std::endl; }
    MyClass(const MyClass& x) { std::cout << "run copy constructor MyClass::MyClass(const MyClass&)" << std::endl; }
    MyClass& operator = (const MyClass& x) { std::cout << "run assignation MyClass::operator=(const MyClass&)" << std::endl; }
};

MyClass my_function()
{
  std::cout << "run my_function()" << std::endl;
  MyClass a;
  std::cout << "my_function is going to return a..." << std::endl;
  return a;
}

int main(int argc, char** argv)
{
  MyClass b = my_function();

  MyClass c;
  c = my_function();

  return 0;
}

出力は次のとおりです。

run my_function()
run constructor MyClass::MyClass()
my_function is going to return a...
run constructor MyClass::MyClass()
run my_function()
run constructor MyClass::MyClass()
my_function is going to return a...
run assignation MyClass::operator=(const MyClass&)
run destructor MyClass::~MyClass()
run destructor MyClass::~MyClass()
run destructor MyClass::~MyClass()

この例はC ++ 03コンテキストで提供されたものであり、C ++> = 11の場合は改善される可能性があることに注意してください。


1
この例は、コピーコンストラクターとコピー割り当て演算子だけでなく、移動コンストラクターと移動割り当て演算子も含まれている場合、より完全になります。(移動機能が存在しない場合は、代わりにコピー機能が使用されます。)
一部のガイ

@SomeGuy同意しますが、C ++ 11は使用しません。私が持っていない知識を提供することはできません。メモを追加します。C ++> = 11の回答を自由に追加してください。:-)
Caduchon

-5

私は同意しないとんはお勧めしませ戻るにはvector

vector <double> vectorial(vector <double> a, vector <double> b)
{
    vector <double> c{ a[1] * b[2] - b[1] * a[2], -a[0] * b[2] + b[0] * a[2], a[0] * b[1] - b[0] * a[1] };
    return c;
}

これははるかに高速です:

void vectorial(vector <double> a, vector <double> b, vector <double> &c)
{
    c[0] = a[1] * b[2] - b[1] * a[2]; c[1] = -a[0] * b[2] + b[0] * a[2]; c[2] = a[0] * b[1] - b[0] * a[1];
}

Visual Studio 2017でテストしましたが、リリースモードでは次の結果が得られました。

参照による8.01 MOPs
5.09 MOPs戻りベクトル

デバッグモードでは、事態はさらに悪化します。

参照による0.053 MOPS
戻りベクトルによる0.034 MOP


-10

これは実際には設計の失敗です。プリミティブではないものに対しては、比較的簡単なことではないものに対して戻り値を使用するべきではありません。

理想的なソリューションは、参照/ポインターの決定を伴う戻りパラメーターと、記述子としての「const \ 'y \' ness」の適切な使用を通じて実装する必要があります。

さらに、CおよびC ++の配列のラベルは事実上ポインターであり、そのサブスクリプションは事実上オフセットまたは加算記号であることを理解する必要があります。

したがって、ラベルまたはptr array_ptr ===配列ラベル、つまりfoo [offset]を返すことは、メモリポインターの場所foo + returnタイプのオフセットのreturn要素を実際に言っていることになります。


5
..........何。「デザインの失敗」のような非難を投げかける資格がないのは明らかだ。そして実際に、RVOと移動操作で値のセマンティクスの推進はの一つであるメイン成功現代のC ++スタイルのES。しかし、あなたは生の配列とポインタについて考えるのに行き詰まっているようですので、あなたがそれを理解することを期待しません。
underscore_d
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.