Mengapa tidak masalah mengembalikan 'vektor' dari suatu fungsi?

108

Harap pertimbangkan kode ini. Saya telah melihat kode jenis ini beberapa kali. wordsadalah vektor lokal. Bagaimana mungkin mengembalikannya dari suatu fungsi?

Bisakah kita jamin dia tidak akan mati?

 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;
}
Pranit Kothari
sumber
18
Itu disalin saat kembali.
songyuanyao
6
Tidak ada yang menjamin .. Ini akan mati, tapi setelah disalin.
Maroun
7
Anda hanya memiliki masalah jika fungsi Anda mengembalikan referensi:std::vector<std::string>&
Caduchon
14
@songyuanyao tidak akan dipindahkan.
kanan
15
@songyuanyao Ya. C ++ 11 adalah standar saat ini, jadi C ++ 11 adalah C ++.
sebelah kanan

Jawaban:

68

Bisakah kita jamin dia tidak akan mati?

Selama tidak ada referensi yang dikembalikan, tidak masalah untuk melakukannya. wordsakan dipindahkan ke variabel yang menerima hasilnya.

Variabel lokal akan keluar dari ruang lingkup. setelah dipindahkan (atau disalin).

πάντα ῥεῖ
sumber
2
Tetapi apakah efisien atau memiliki masalah kinerja mengatakan untuk vektor yang dapat menampung 1000 entri?
zar
@zadane Apakah ini yang dipertanyakan? Juga saya sebutkan pemindahan yang akan menghindari untuk mengambil salinan nilai pengembalian sebenarnya (tersedia setidaknya dengan standar saat ini).
πάντα ῥεῖ
2
Tidak, tidak benar-benar dalam pertanyaan, tetapi saya mencari jawaban dari perspektif itu secara mandiri. Saya tidak tahu apakah saya memposting pertanyaan saya, saya khawatir mereka akan menandainya sebagai duplikat dari ini :)
zar
@zadane "Saya khawatir mereka akan menandainya sebagai duplikat dari ini" Mungkin saja. Lihat saja jawaban yang dipilih lebih tinggi . Bahkan untuk implementasi yang lebih lama Anda tidak perlu khawatir, itu sebagian besar akan dioptimalkan dengan benar oleh kompiler tersebut.
πάντα ῥεῖ
107

Sebelum C ++ 11:

Fungsi ini tidak akan mengembalikan variabel lokal, melainkan salinannya. Namun kompilator Anda mungkin melakukan pengoptimalan di mana tidak ada tindakan penyalinan yang sebenarnya dilakukan.

Lihat pertanyaan & jawaban ini untuk lebih jelasnya.

C ++ 11:

Fungsi tersebut akan memindahkan nilai. Lihat jawaban ini untuk lebih jelasnya.

Tim Meyer
sumber
2
Ini akan dipindahkan, bukan disalin. Ini dijamin.
kanan
1
Apakah ini juga berlaku untuk C ++ 10?
Tim Meyer
28
Tidak ada yang namanya C ++ 10.
kanan
C ++ 03 tidak memiliki semantik bergerak (tetapi salinan mungkin telah dihilangkan), tetapi C ++ adalah C ++ 11 dan pertanyaannya adalah tentang C ++.
kanan
19
Ada tag terpisah untuk pertanyaan eksklusif untuk C ++ 11. Banyak dari kita, terutama programmer di perusahaan besar masih terjebak pada kompiler yang belum sepenuhnya mendukung C ++ 11. Saya memperbarui pertanyaan agar akurat untuk kedua standar.
Tim Meyer
26

Saya pikir Anda mengacu pada masalah di C (dan C ++) yang mengembalikan array dari fungsi tidak diizinkan (atau setidaknya tidak akan berfungsi seperti yang diharapkan) - ini karena pengembalian array akan (jika Anda menuliskannya di bentuk sederhana) mengembalikan pointer ke array aktual di stack, yang kemudian segera dihapus saat fungsi kembali.

Tapi dalam kasus ini, itu berfungsi, karena std::vectoradalah kelas, dan kelas, seperti struct, dapat (dan akan) disalin ke konteks pemanggil. [Sebenarnya, sebagian besar kompiler akan mengoptimalkan jenis salinan khusus ini menggunakan sesuatu yang disebut "Pengoptimalan Nilai Kembali", yang secara khusus diperkenalkan untuk menghindari penyalinan objek besar saat dikembalikan dari suatu fungsi, tetapi itu adalah pengoptimalan, dan dari perspektif pemrogram, itu akan berperilaku seolah-olah konstruktor tugas dipanggil untuk objek]

Selama Anda tidak mengembalikan pointer atau referensi ke sesuatu yang ada di dalam fungsi yang dikembalikan, Anda baik-baik saja.

Mats Petersson
sumber
13

Untuk memahami perilakunya dengan baik, Anda dapat menjalankan kode ini:

#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;
}

Outputnya adalah sebagai berikut:

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

Perhatikan bahwa contoh ini disediakan dalam konteks C ++ 03, dapat ditingkatkan untuk C ++> = 11

Caduchon
sumber
1
Contoh ini akan lebih lengkap jika menyertakan konstruktor pindahan dan operator penugasan pindahan juga, dan tidak hanya menyalin konstruktor dan menyalin operator penugasan. (Jika fungsi pemindahan tidak ada, yang menyalin akan digunakan sebagai gantinya.)
Some Guy
@SomeGuy Saya setuju, tapi saya tidak menggunakan C ++ 11. Saya tidak bisa memberikan pengetahuan yang tidak saya miliki. Saya menambahkan catatan. Jangan ragu untuk menambahkan jawaban untuk C ++> = 11. :-)
Caduchon
-5

Saya tidak setuju dan tidak merekomendasikan untuk mengembalikan 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;
}

Ini jauh lebih cepat:

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];
}

Saya menguji pada Visual Studio 2017 dengan hasil berikut dalam mode rilis:

8,01 MOP dengan referensi
vektor pengembalian 5,09 MOP

Dalam mode debug, segalanya menjadi lebih buruk:

0,053 MOPS dengan referensi
0,034 MOPs dengan vektor kembali

mathengineer
sumber
-10

Ini sebenarnya adalah kegagalan desain. Anda tidak boleh menggunakan nilai pengembalian untuk apa pun yang bukan primitif untuk apa pun yang relatif tidak sepele.

Solusi ideal harus diimplementasikan melalui parameter return dengan keputusan referensi / pointer dan penggunaan yang tepat dari "const \ 'y \' ness" sebagai deskriptor.

Di atas ini, Anda harus menyadari bahwa label pada larik di C dan C ++ secara efektif adalah penunjuk dan langganannya secara efektif merupakan simbol offset atau penambahan.

Jadi label atau ptr array_ptr === label array sehingga mengembalikan foo [offset] benar-benar mengatakan elemen kembalian di lokasi penunjuk memori foo + offset tipe kembalian.

Newbstarr
sumber
5
..........apa. Tampak jelas bahwa Anda tidak memenuhi syarat untuk melemparkan tuduhan seperti "kegagalan desain". Dan pada kenyataannya, promosi nilai semantik oleh operasi RVO dan bergerak adalah salah satu yang utama keberhasilan es dari C ++ gaya modern. Tapi Anda tampaknya terjebak memikirkan array mentah dan pointer, jadi saya tidak mengharapkan Anda untuk memahami itu.
underscore_d