16

Tôi nhận thức được thực tế rằng việc gán giá trị cho một tham chiếu const lvalue kéo dài tuổi thọ thời gian cho đến khi kết thúc phạm vi. Tuy nhiên, nó không phải là rõ ràng với tôi khi sử dụng này và khi nào phải dựa vào tối ưu hóa giá trị trả về.const tham chiếu đến tối ưu hóa tạm thời so với giá trị trả về

LargeObject lofactory(...) { 
    // construct a LargeObject in a way that is OK for RVO/NRVO 
} 

int main() { 
    const LargeObject& mylo1 = lofactory(...); // using const& 
    LargeObject mylo2 = lofactory(...); // same as above because of RVO/NRVO ? 
} 

Theo Scot Meyers' C hiệu quả hơn ++ (khoản 20) phương pháp thứ hai có thể được tối ưu hóa bởi trình biên dịch để xây dựng các đối tượng tại chỗ (trong đó sẽ là lý tưởng và chính xác những gì một cố gắng để đạt được với các const& trong phương pháp đầu tiên).

  1. Có quy tắc hoặc phương pháp hay nhất được chấp nhận chung khi sử dụng const& cho thời gian và thời điểm dựa vào RVO/NRVO không?
  2. Có thể có tình huống trong đó sử dụng phương pháp const& tồi tệ hơn việc không sử dụng phương pháp này? (Tôi đang nghĩ đến ví dụ về C++ 11 ngữ nghĩa di chuyển nếu LargeObject đã thực hiện những ...)

Trả lời

12

Hãy xem xét các trường hợp đơn giản nhất:

lofactory(...).some_method(); 

Trong trường hợp này một bản sao từ lofactory đến ngữ cảnh người gọi là có thể – nhưng nó có thể được tối ưu hóa bằng cách RVO/NRVO.


LargeObject mylo2 (lofactory(...)); 

Trong trường hợp này bản sao có thể là:

  1. Return tạm từ lofactory đến người gọi bối cảnh – có thể được tối ưu hóa đi RVO/NRVO
  2. Copy- xây dựng mylo2 từ tạm – có thể được tối ưu hóa đi sao chép sự bỏ bớt

const LargeObject& mylo1 = lofactory(...); 

Trong trường hợp này, có một bản sao vẫn còn có thể:

  1. Return tạm từ lofactory đến ngữ cảnh người gọi – có thể được tối ưu hóa awa y by RVO/NRVO (quá!)

Một tham chiếu sẽ liên kết với điều này tạm thời.


Vì vậy,

Có bất kỳ quy tắc chung được chấp nhận hoặc thực hành tốt nhất khi sử dụng const & để temporaries và khi nào thì dựa vào RVO/NRVO?

Như tôi đã nói ở trên, ngay cả trong một trường hợp với const&, một bản sao unnecesary là có thể, và nó có thể được tối ưu hóa đi RVO/NRVO.

Nếu trình biên dịch của bạn áp dụng RVO/NVRO trong một số trường hợp, thì rất có khả năng nó sẽ thực hiện copy-elision ở giai đoạn 2 (ở trên). Bởi vì trong trường hợp đó, copy-elision đơn giản hơn nhiều so với NRVO.

Nhưng, trong trường hợp xấu nhất, bạn sẽ có một bản sao cho trường hợp const& và hai bản sao khi bạn bắt đầu giá trị.

Có thể có tình huống trong đó sử dụng phương pháp const & tệ hơn không sử dụng?

Tôi không nghĩ rằng có những trường hợp như vậy. Ít nhất trừ khi trình biên dịch của bạn sử dụng các quy tắc lạ phân biệt const&. (Ví dụ về một tình huống tương tự, tôi nhận thấy rằng MSVC không làm NVRO để khởi tạo tổng hợp.)

(Tôi đang nghĩ ví dụ về C++ 11 ngữ nghĩa di chuyển nếu LargeObject có những ngữ nghĩa được triển khai ...)

trong C++ 11, nếu LargeObject có di chuyển ngữ nghĩa, sau đó trong trường hợp xấu nhất, bạn sẽ có một động thái đối với trường hợp const&, và hai di chuyển khi bạn init giá trị. Vì vậy, const& vẫn còn tốt hơn một chút.


Vì vậy, một quy tắc tốt sẽ luôn luôn ràng buộc là tạm thời để const & nếu có thể, vì nó có thể ngăn chặn một bản sao nếu trình biên dịch không thực hiện một bản sao-sự bỏ bớt vì một lý do?

Không biết bối cảnh ứng dụng thực tế, điều này có vẻ như là một quy tắc tốt.

Trong C++ 11, bạn có thể liên kết tham chiếu tạm thời với rvalue - LargeObject & &. Vì vậy, tạm thời như vậy có thể được sửa đổi.


Bằng cách này, hãy chuyển mô phỏng ngữ nghĩa có sẵn cho C++ 98/03 bằng các thủ thuật khác nhau. Ví dụ:

Tuy nhiên, ngay cả khi có ngữ nghĩa di chuyển - có các đối tượng không thể di chuyển rẻ. Ví dụ, lớp ma trận 4x4 với dữ liệu kép [4] [4] bên trong. Vì vậy, Copy-elision RVO/NRVO vẫn rất quan trọng, ngay cả trong C++ 11. Và bằng cách này, khi Copy-elision/RVO/NRVO xảy ra - nó nhanh hơn di chuyển.


PS, trong những trường hợp thực tế, có một số điều bổ sung mà cần được xem xét:

Ví dụ, nếu bạn có hàm trả về vector, ngay cả khi di chuyển/RVO/NRVO/Copy-sự bỏ bớt sẽ được áp dụng - nó vẫn có thể không hiệu quả 100%. Ví dụ, hãy xem xét trường hợp sau đây:

while(/*...*/) 
{ 
    vector<some> v = produce_next(/* ... */); // Move/RVO/NRVO are applied 
    // ... 
} 

Nó sẽ hiệu quả hơn để thay đổi mã để:

vector<some> v; 
while(/*...*/) 
{ 
    v.clear(); 

    produce_next(v); // fill v 
    // or something like: 
    produce_next(back_inserter(v)); 
    // ... 
} 

Bởi vì trong trường hợp này, bộ nhớ đã được phân bổ trong vector có thể được tái sử dụng khi v.capacity() là đủ, mà không cần phải phân bổ mới bên trong produce_next trên mỗi lần lặp.

+0

Vì vậy, một quy tắc tốt sẽ là _always_ ràng buộc thời gian để 'const &' nếu có thể, vì nó có thể ngăn chặn một bản sao nếu trình biên dịch không thực hiện một bản sao-elision vì lý do nào? –

+0

Chấp nhận nó, cảm ơn bạn! –

7

Nếu bạn viết lớp học của bạn lofactory như thế này:

LargeObject lofactory(...) { 
    // figure out constructor arguments to build a large object 
    return { arg1, arg2, arg3 } // return statement with a braced-init-list 
} 

Trong trường hợp này không có RVO/NRVO, đó là xây dựng trực tiếp. Phần 6.6.3 của tiêu chuẩn nói “A return statement with a braced-init-list khởi tạo đối tượng hoặc tham chiếu được trả về từ hàm bằng cách sao chép danh sách-khởi tạo (8.5.4) từ danh sách khởi tạo được chỉ định.”

Sau đó, nếu bạn chụp đối tượng của bạn với

LargeObject&& mylo = lofactory(...); 

sẽ không có bất kỳ sao chép, tuổi thọ sẽ là những gì bạn mong muốn, và bạn có thể sửa đổi mylo.

Và tất cả không sao chép ở bất kỳ đâu, được bảo đảm.

+0

Chà, thật thú vị! Không biết điều đó, cảm ơn! –

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