Bir fonksiyondan bir 'vektör' döndürmek neden uygundur?


108

Lütfen bu kodu dikkate alın. Bu tür bir kodu defalarca gördüm. wordsyerel bir vektördür. Onu bir fonksiyondan döndürmek nasıl mümkün olabilir?

Ölmeyeceğini garanti edebilir miyiz?

 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
Döndüğünde kopyalanır.
songyuanyao

6
Hiç kimse garanti .. O olacaktır die ama kopyalanan sonra.
Maroun

7
Yalnızca işleviniz bir referans döndürürse sorun std::vector<std::string>&
yaşarsınız

14
@songyuanyao hayır, taşınacak.
sağ sayfa

15
@songyuanyao Evet. C ++ 11 şu anki standarttır, dolayısıyla C ++ 11 C ++ 'dır.
sağ sayfa

Yanıtlar:


68

Ölmeyeceğini garanti edebilir miyiz?

Döndürülen bir referans olmadığı sürece, bunu yapmak tamamen iyidir. wordssonucu alan değişkene taşınacaktır.

Yerel değişken kapsam dışına çıkacaktır. taşındıktan (veya kopyalandıktan) sonra.


2
Ancak, 1000 girişi tutan vektör için verimli mi yoksa performans endişeleri var mı?
zar

@zadane Bu söz konusu muydu? Ayrıca bahsettiğim hareketli (cari standardı ile en azından mevcut) aslında dönüş değeri bir kopyasını almaya önlemek olacaktır.
πάντα ῥεῖ

2
Hayır, aslında soruda değil ama bu açıdan bağımsız olarak cevap arıyordum.
Sorumu

@zadane "Korkarım bunu kopyası olarak işaretleyecekler" Olabilir. Sadece en yüksek oyu alan cevaba bir göz atın . Daha eski uygulamalar için bile endişelenmemelisiniz, bunlar yine de bu derleyiciler tarafından çoğunlukla doğru şekilde optimize edilecektir.
πάντα ῥεῖ

107

Ön C ++ 11:

İşlev yerel değişkeni döndürmez, bunun yerine onun bir kopyasını döndürür. Derleyiciniz ancak gerçek kopyalama eyleminin yapılmadığı bir optimizasyon gerçekleştirebilir.

Daha fazla ayrıntı için bu soruya ve cevaba bakın.

C ++ 11:

İşlev, değeri taşıyacaktır. Daha fazla ayrıntı için bu yanıta bakın.


2
Kopyalanmayacak, taşınacak. Bu garantilidir.
sağ sayfa

1
Bu C ++ 10 için de geçerli mi?
Tim Meyer

28
C ++ 10 diye bir şey yoktur.
sağ sayfa

C ++ 03'ün hiçbir hareket semantiği yoktu (ancak kopya atlanmış olabilir), ancak C ++ C ++ 11 ve soru C ++ hakkındaydı.
sağ sayfa

19
C ++ 11'e özel sorular için ayrı bir etiket vardır. Birçoğumuz, özellikle de büyük şirketlerdeki programcılar, henüz C ++ 11'i tam olarak desteklemeyen derleyicilere bağlı kaldık. Soruyu her iki standart için de doğru olacak şekilde güncelledim.
Tim Meyer

26

Sanırım C (ve C ++ 'da) bir işlevden bir dizi döndürmeye izin verilmeyen (veya en azından beklendiği gibi çalışmayacağı) soruna atıfta bulunuyorsunuz - çünkü dizi dönüşü olacaktır (eğer onu yazarsanız basit biçim), yığındaki gerçek diziye bir gösterici döndürür, bu daha sonra işlev döndüğünde hemen kaldırılır.

Ancak bu durumda işe yarar, çünkü std::vectorbir sınıftır ve structs gibi sınıflar çağıranlar bağlamına kopyalanabilir (ve kopyalanacaktır). [Aslında, çoğu derleyici, bir işlevden döndürüldüklerinde büyük nesnelerin kopyalanmasını önlemek için özel olarak sunulan "Dönüş Değeri Optimizasyonu" adı verilen bir şey kullanarak bu belirli türdeki kopyayı optimize edecektir, ancak bu bir optimizasyondur ve programcıların bakış açısından bakıldığında nesne için atama yapıcısı çağrılmış gibi davranın]

Geri dönen işlevin içindeki bir şeye bir işaretçi veya referans döndürmediğiniz sürece, sorun yok.


13

Davranışı iyi anlamak için şu kodu çalıştırabilirsiniz:

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

Çıktı şu şekildedir:

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

Bu örneğin C ++ 03 bağlamında sağlandığını, C ++> = 11 için geliştirilebileceğini unutmayın.


1
Bu örnek, yalnızca kopya yapıcı ve kopya atama operatörünü değil, bir taşıma yapıcısı ve bir taşıma atama operatörü de içeriyorsa daha eksiksiz olacaktır. (Taşıma işlevleri mevcut değilse, bunun yerine kopyalar kullanılacaktır.)
Bazı Guy

@SomeGuy Katılıyorum, ancak C ++ 11 kullanmıyorum. Sahip olmadığım bilgiyi sağlayamıyorum. Bir not ekliyorum. C ++> = 11 için bir yanıt eklemekten çekinmeyin. :-)
Caduchon

-5

Ben aynı fikirde değilim ve do tavsiye bir dönüş 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;
}

Bu çok daha hızlı:

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

Yayın modunda aşağıdaki sonuçlarla Visual Studio 2017'de test ettim:

Referans ile 8.01 MOP'lar
5.09 MOP geri dönen vektör

Hata ayıklama modunda işler çok daha kötüdür:

Referansla 0,053 MOPS
dönüş vektörüne göre 0,034 MOP


-10

Bu aslında bir tasarım hatasıdır. Nispeten önemsiz olmayan herhangi bir şey için ilkel olmayan herhangi bir şey için bir dönüş değeri kullanmamalısınız.

İdeal çözüm, referans / işaretçi hakkında bir karar ve tanımlayıcı olarak bir "sabit" in doğru kullanımı ile bir dönüş parametresi aracılığıyla uygulanmalıdır.

Bunun da ötesinde, C ve C ++ 'daki bir dizideki etiketin etkili bir şekilde bir işaretçi olduğunu ve aboneliğinin etkin bir şekilde bir ofset veya bir toplama sembolü olduğunu anlamalısınız.

Dolayısıyla, etiket veya ptr array_ptr === dizi etiketi böylece foo [offset] döndürür, gerçekten de bellek işaretçi konumunda foo + dönüş türünün ofsetinde dönüş öğesi söylüyor.


5
..........ne. Açıkça görülüyor ki, "tasarım başarısızlığı" gibi suçlamaları atmaya yetkili değilsiniz. Ve aslında, RVO ve taşıma işlemlerini tarafından değer semantik tanıtım biridir ana başarı Modern C ++ tarzı es. Ama ham diziler ve işaretçiler hakkında düşünmeye takılı kalmış görünüyorsunuz, bu yüzden bunu anlamanızı beklemiyorum.
alt
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.