2012-07-08 55 views
9

Tôi đang học C++ từ một nền tảng chủ yếu là C và Java và tôi tò mò về cách tốt nhất để trả về một đối tượng trong C++ mà không phải sao chép đối tượng. Từ sự hiểu biết của tôi, C++ 11 đã giới thiệu các tham chiếu rvalue (& &) để di chuyển dữ liệu từ một biến tạm thời (ngược lại với việc sao chép). Ví dụ:Trả về một đối tượng trong C++

std::string getStr(){ 
    return "Hello"; 
} 

std::string &&hello = getStr(); 

Một cách khác tôi có thể nghĩ là sử dụng con trỏ dùng chung.

std::tr1::shared_ptr<std::string> getStr(){ 

    std::tr1::shared_ptr<std::string> hello(new std::string("Hello")); 
    return hello; 

} 

auto hello = getStr(); 

Tôi nghĩ có lẽ một tham chiếu rvalue tốt hơn nhưng tôi muốn có ý kiến ​​thứ hai trước khi tôi sử dụng nó. Cái nào tốt hơn?

Ngoài ra, các trường trong lớp là tham chiếu giá trị nếu chúng không được đặt bằng cách sử dụng hàm tạo? Ví dụ:

class StringHolder{ 

    public: 
    std::string &&str; 
}; 

StringHolder sh; 
sh.str = getStr(); 

Cảm ơn các bạn rất nhiều!

+1

Bạn có thể tìm thấy [Giới thiệu tóm tắt về tham chiếu Rvalue] (http://www.artima.com/cppsource/rvalue.html) hữu ích –

+0

Triển khai hiện đại của thư viện chuẩn sẽ sử dụng tối ưu hóa chuỗi nhỏ, do đó phân bổ động chuỗi "Hello" có thể chậm hơn so với chỉ trả về một giá trị. – bames53

Trả lời

10

Câu hỏi này có thể sẽ bị đóng là trùng lặp. Đó là một câu hỏi không được hỏi. Tuy nhiên tôi vẫn muốn trả lời.

Trong C++ 11, khi bạn là khách hàng của một đối tượng như std::string, bạn nên vượt qua nó xung quanh bằng giá trị và không phải lo lắng quá nhiều về hiệu quả:

std::string getStr(){ 
    return "Hello"; 
} 

std::string hello = getStr(); 

Ở cấp độ này bạn không cần để được quan tâm với tham chiếu rvalue. Chỉ cần biết rằng std::string có thể "sao chép" từ các giá trị (chẳng hạn như lợi nhuận từ getStr()) rất một cách hiệu quả. "Bản sao" này thực sự được gọi là "di chuyển" và được kích hoạt tự động vì bạn đang sao chép từ một giá trị (một tạm thời ẩn danh).

Đừng cố gắng tối ưu hóa sao chép bằng cách hoàn nguyên về tính tham chiếu. Chỉ sử dụng tính tham chiếu nếu bạn cần ngữ nghĩa quyền sở hữu được chia sẻ. Câu này không phải là luôn là đúng. Nhưng đối với việc học những điều cơ bản, nó là đủ gần mà nó là một nguyên tắc tốt để làm theo.

Bạn cần bắt đầu lo lắng về tài liệu tham khảo rvalue khi bạn thiết kế lớp học của bạn mà cần phải được thông qua xung quanh bởi giá trị:

class Widget 
{ 
    // pointer to heap data 
public: 
    // ... 
}; 

Đối với một giới thiệu ngắn gọn để rvalue tài liệu tham khảo và di chuyển ngữ nghĩa, N2027 là một hướng dẫn đàng hoàng. N2027 là quá dài để được dán ở đây, nhưng đủ ngắn để dễ đọc. std::string theo những điều cơ bản được đặt ra trong N2027 để cho phép bạn vượt qua nó bằng giá trị cảm giác tội lỗi miễn phí. Và bạn có thể làm theo các mẫu thiết kế tương tự trong số Widget của mình.

+0

Cảm ơn rất nhiều vì điều này! Tôi dần dần hiểu được hướng dẫn và khái niệm về các nhà thầu di chuyển.Do hầu hết các đối tượng trong std có constructor di chuyển như một vector (có nghĩa là, nó là thông minh để trả về một vector lớn theo giá trị)? Ngoài ra, trong lớp Widget của bạn, là chú thích "con trỏ tới đống dữ liệu" chỉ dành riêng cho ví dụ này hoặc sử dụng con trỏ như các trường trái ngược với kiểu giá trị (mặc dù, tôi sẽ phỏng đoán sử dụng con trỏ hoặc giá trị trường phụ thuộc vào tình hình)? – DanB91

+0

Ngoài ra, tôi biết điều này phụ thuộc vào các nhà xây dựng di chuyển của đối tượng, nhưng tôi giả định, như một quy luật chung, nếu bạn có obj o = std :: di chuyển (p) ;, p có lẽ sẽ không bao giờ được sử dụng lại, đúng không? – DanB91

+0

@ DanB91: Dù sao cho đến khi nó được gán cho. – ildjarn

1

Các tham chiếu giá trị sẽ nhanh hơn nhiều vì chúng là một tính năng ngôn ngữ nội tại, chứ không phải là lớp đếm tham chiếu. Sử dụng một con trỏ thông minh hoặc chia sẻ ở đây sẽ không đạt được, và làm giảm đáng kể sự rõ ràng. Tuy nhiên, tài liệu tham khảo giá trị rvalue là một phần của ngôn ngữ được thiết kế cho loại điều này. Sử dụng tham chiếu rvalue.

2

Thường thì bạn có thể tránh các bản sao bằng cách trả về tham chiếu const cho biến thành viên. Ví dụ:

class Circle { 
    Point center; 
    float radius; 
public: 
    const Point & getCenter() const { return center; }; 
}; 

này tương đương với return &center trong thi công và trong thực tế có thể sẽ được inlined chỉ đơn giản là kết quả trực tiếp (read-only) tiếp cận của các thành viên từ người gọi.

Trong trường hợp bạn xây dựng tạm thời để trả về trình biên dịch có thể bỏ qua bản sao bổ sung làm tối ưu hóa, không yêu cầu cú pháp đặc biệt nào từ phía bạn.

0

Nếu bạn muốn sử dụng con trỏ thông minh trong trường hợp này, có thể unique_ptr là lựa chọn tốt hơn.

2

Theo mặc định, bạn chỉ cần chuyển mọi thứ theo giá trị. Nếu một hàm không cần phải sửa đổi hoặc sao chép một tham số thì nó có thể được thực hiện bởi const&, nhưng nếu không chỉ lấy tham số theo giá trị. Trả về các đối tượng theo giá trị và để nó di chuyển ngữ nghĩa hoặc RVO để tránh một bản sao.


C++ sử dụng và khuyến khích các đối tượng đi qua 'theo giá trị'. Di chuyển ngữ nghĩa là một tính năng cho phép mã thực hiện điều này trong khi tạo ít bản sao hơn trước khi chúng được giới thiệu. Thông thường mã không cần phải sử dụng các loại tham chiếu rvalue trực tiếp để đạt được lợi ích của ngữ nghĩa di chuyển. Điều này là do các đối tượng tạm thời là các giá trị và độ phân giải quá tải sẽ tự động chọn các phương thức có tham chiếu rvalue.

Ví dụ:

std::string getStr(){ 
    return "Hello"; 
} 

std::string hello = getStr(); 

Mã này sử dụng di chuyển ngữ nghĩa. return "Hello" xây dựng một chuỗi tạm thời và do đó hàm tạo được sử dụng để khởi tạo đối tượng giá trị trả về sẽ là hàm tạo di chuyển (mặc dù thực sự tối ưu hóa giá trị trả về gần như chắc chắn sẽ tránh được việc di chuyển này). Sau đó, giá trị trả về bởi getStr() là tạm thời, do đó hàm tạo được chọn cho string hello lại là một hàm tạo di chuyển.

Tôi nghĩ bạn cũng có thể làm std::string &&hello = getStr();, nhưng có thể không cần thiết vì hello sẽ được di chuyển xây dựng.

Một cách khác tôi có thể nghĩ đến là sử dụng một con trỏ chia sẻ

Tôi không nghĩ rằng con trỏ thông minh là một nói chung là một ý tưởng tốt, trừ khi bạn thực sự cần ngữ nghĩa sở hữu cụ thể mà họ cung cấp. Việc sử dụng tính năng vượt qua 'theo giá trị' đơn giản là một mặc định tốt và các ngữ nghĩa di chuyển thường cho phép bạn sử dụng nó mà không cần sao chép thêm.

Ngoài ra, các trường trong lớp là tham chiếu rvalue nếu chúng không được đặt bằng cách sử dụng hàm tạo?

Không, tham chiếu thành viên thường không phải là ý tưởng hay. Chúng làm cho mọi thứ phức tạp hơn và bạn thậm chí không được phép sử dụng chúng như ví dụ của bạn cho thấy. Nếu mã ví dụ của bạn được cho phép thì sh.str = getStr(); sẽ là hành vi không xác định, bởi vì bạn đang cố gán cho một đối tượng không tồn tại. Nó giống như std::string *str; *str = getStr();.

+0

Từ những gì tôi thu thập được, tôi nghĩ rằng các tham chiếu rvalue lưu các giá trị trả về khỏi bị hủy. Trong thực tế, tôi đã viết một cách nhanh chóng "std :: string && hello = getStr();" chương trình và nó làm việc tốt (như trái ngược với làm chuỗi * hello; * hello = getStr() ;, mà rõ ràng là gây ra một lỗi seg). Nhưng bây giờ tôi dần dần hiểu được việc sử dụng && và sẽ đưa ra lời khuyên về việc trả về các đối tượng theo giá trị (miễn là chúng nhỏ hoặc triển khai hàm tạo di chuyển). Cảm ơn! – DanB91

+0

@ DanB91 Thực ra tôi nghĩ bạn đúng là bạn có thể nắm bắt được một giá trị trả về với một tham chiếu rvalue. – bames53

Các vấn đề liên quan