Tại sao có thể trả về 'vectơ' từ một hàm?


108

Vui lòng xem xét mã này. Tôi đã thấy loại mã này vài lần. wordslà một vectơ địa phương. Làm cách nào để trả về nó từ một hàm?

Chúng ta có thể đảm bảo nó sẽ không chết?

 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
Nó được sao chép khi trả lại.
songyuanyao

6
Không ai đảm bảo .. Nó sẽ chết, nhưng sau khi nó được sao chép.
Maroun

7
Bạn chỉ có một vấn đề nếu chức năng của bạn trả về một tài liệu tham khảo:std::vector<std::string>&
Caduchon

14
@songyuanyao không, nó sẽ được chuyển đi.
sang phải

15
@songyuanyao Có. C ++ 11 là tiêu chuẩn hiện tại, vì vậy C ++ 11 là C ++.
sang phải

Câu trả lời:


68

Chúng ta có thể đảm bảo nó sẽ không chết?

Miễn là không có tham chiếu nào được trả về, bạn hoàn toàn có thể làm như vậy. wordssẽ được chuyển đến biến nhận kết quả.

Biến cục bộ sẽ vượt ra khỏi phạm vi. sau khi nó được di chuyển (hoặc sao chép).


2
Nhưng liệu có hiệu quả hoặc có bất kỳ mối quan tâm nào về hiệu suất nói đối với vector có thể chứa 1000 mục nhập không?
zar

@zadane Có câu hỏi này không? Ngoài ra, tôi đã đề cập đến việc di chuyển sẽ tránh lấy một bản sao của giá trị trả về trên thực tế (ít nhất có sẵn với tiêu chuẩn hiện tại).
πάντα ῥεῖ

2
Không thực sự trong câu hỏi nhưng tôi đã tìm kiếm câu trả lời từ quan điểm đó một cách độc lập. Tôi không biết nếu tôi đăng câu hỏi của mình, tôi sợ họ sẽ đánh dấu nó trùng lặp với câu hỏi này :)
zar

@zadane "Tôi sợ họ sẽ đánh dấu nó trùng lặp với cái này" Cũng có thể. Chỉ cần nhìn vào câu trả lời được bình chọn cao hơn . Ngay cả đối với các triển khai cũ hơn, bạn cũng không nên lo lắng, dù sao thì chúng hầu hết sẽ được tối ưu hóa chính xác bởi các trình biên dịch đó.
πάντα ῥεῖ

107

Pre C ++ 11:

Hàm sẽ không trả về biến cục bộ mà là một bản sao của nó. Tuy nhiên, trình biên dịch của bạn có thể thực hiện tối ưu hóa mà không có hành động sao chép thực tế nào được thực hiện.

Xem câu hỏi và câu trả lời này để biết thêm chi tiết.

C ++ 11:

Hàm sẽ di chuyển giá trị. Xem câu trả lời này để biết thêm chi tiết.


2
Nó sẽ được di chuyển, không được sao chép. Điều này được đảm bảo.
sang phải

1
Điều này có áp dụng cho C ++ 10 không?
Tim Meyer

28
Không có cái gọi là C ++ 10.
sang phải

C ++ 03 không có ngữ nghĩa di chuyển (nhưng bản sao có thể đã được làm sáng tỏ), nhưng C ++ là C ++ 11 và câu hỏi là về C ++.
sang phải

19
Có một thẻ riêng cho các câu hỏi dành riêng cho C ++ 11. Nhiều người trong chúng ta, đặc biệt là các lập trình viên ở các công ty lớn hơn vẫn còn mắc kẹt với các trình biên dịch chưa hỗ trợ đầy đủ C ++ 11. Tôi đã cập nhật câu hỏi để chính xác cho cả hai tiêu chuẩn.
Tim Meyer

26

Tôi nghĩ rằng bạn đang đề cập đến vấn đề trong C (và C ++) rằng việc trả về một mảng từ một hàm không được phép (hoặc ít nhất là sẽ không hoạt động như mong đợi) - điều này là do trả về mảng sẽ (nếu bạn viết nó vào dạng đơn giản) trả về một con trỏ đến mảng thực trên ngăn xếp, sau đó sẽ bị xóa ngay khi hàm trả về.

Nhưng trong trường hợp này, nó hoạt động, bởi vì std::vectorlà một lớp và các lớp, như cấu trúc, có thể (và sẽ) được sao chép vào ngữ cảnh người gọi. [Trên thực tế, hầu hết các trình biên dịch sẽ tối ưu hóa loại bản sao cụ thể này bằng cách sử dụng thứ gọi là "Tối ưu hóa giá trị trả về", được giới thiệu cụ thể để tránh sao chép các đối tượng lớn khi chúng được trả về từ một hàm, nhưng đó là cách tối ưu hóa và từ góc độ lập trình viên, nó sẽ hành xử như thể hàm tạo phép gán được gọi cho đối tượng]

Miễn là bạn không trả về một con trỏ hoặc một tham chiếu đến một cái gì đó nằm trong hàm trả về, bạn vẫn ổn.


13

Để hiểu rõ về hành vi, bạn có thể chạy mã này:

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

Đầu ra như sau:

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

Lưu ý rằng ví dụ này được cung cấp trong ngữ cảnh C ++ 03, nó có thể được cải thiện cho C ++> = 11


1
Ví dụ này sẽ hoàn thiện hơn nếu nó cũng bao gồm một hàm tạo di chuyển và một toán tử gán di chuyển, và không chỉ sao chép hàm tạo và toán tử gán sao chép. (Nếu các chức năng di chuyển không có mặt, các chức năng sao chép sẽ được sử dụng thay thế.)
Some Guy

@SomeGuy Tôi đồng ý, nhưng tôi không sử dụng C ++ 11. Tôi không thể cung cấp kiến ​​thức mà tôi không có. Tôi thêm một ghi chú. Vui lòng thêm câu trả lời cho C ++> = 11. :-)
Caduchon

-5

Tôi không đồng ý và không khuyên bạn nên trả lại 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;
}

Điều này nhanh hơn nhiều:

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

Tôi đã thử nghiệm trên Visual Studio 2017 với kết quả sau ở chế độ phát hành:

8,01 MOP theo tham chiếu
vectơ trả về 5,09 MOPs

Trong chế độ gỡ lỗi, mọi thứ còn tệ hơn nhiều:

0,053 MOPS theo tham chiếu
0,034 MOPs theo vectơ trả về


-10

Đây thực sự là một thất bại của thiết kế. Bạn không nên sử dụng giá trị trả về cho bất kỳ thứ gì không phải là giá trị nguyên thủy cho bất kỳ thứ gì không tương đối tầm thường.

Giải pháp lý tưởng nên được thực hiện thông qua một tham số trả về với quyết định về tham chiếu / con trỏ và sử dụng đúng "const \ 'y \' ness" làm bộ mô tả.

Trên hết, bạn nên nhận ra rằng nhãn trên một mảng trong C và C ++ thực sự là một con trỏ và đăng ký của nó thực sự là một phần bù hoặc một ký hiệu bổ sung.

Vì vậy, nhãn hoặc ptr array_ptr === mảng nhãn do đó trả về foo [offset] thực sự nói phần tử trả về tại vị trí con trỏ bộ nhớ foo + offset của kiểu trả về kiểu.


5
..........gì. Có vẻ như bạn không đủ tư cách để ném ra những lời buộc tội như "thiết kế thất bại". Và trên thực tế, việc thúc đẩy ngữ nghĩa giá trị của RVO và các hoạt động di chuyển là một trong những thành công chính của phong cách C ++ hiện đại. Nhưng dường như bạn đang gặp khó khăn khi nghĩ về các mảng và con trỏ thô, vì vậy tôi sẽ không mong đợi bạn nắm được điều đó.
underscore_d
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.