Sao chép bản sao và tối ưu hóa giá trị trả lại là gì?


377

Bản sao bầu cử là gì? Tối ưu hóa giá trị trả lại (được đặt tên) là gì? Họ ngụ ý gì?

Trong những tình huống họ có thể xảy ra? Hạn chế là gì?


1
Sao chép bầu cử là một cách để xem xét nó; đối tượng bỏ qua hoặc hợp nhất đối tượng (hoặc nhầm lẫn) là một quan điểm khác.
tò mò

Tôi thấy liên kết này hữu ích.
Subleseeker

Câu trả lời:


246

Giới thiệu

Để biết tổng quan về kỹ thuật - bỏ qua câu trả lời này .

Đối với các trường hợp phổ biến khi xảy ra cuộc bầu cử sao chép - bỏ qua câu trả lời này .

Sao chép bản sao là một tối ưu hóa được thực hiện bởi hầu hết các trình biên dịch để ngăn chặn các bản sao bổ sung (có khả năng đắt tiền) trong các tình huống nhất định. Nó làm cho việc trả về theo giá trị hoặc thông qua giá trị khả thi trong thực tế (áp dụng các hạn chế).

Đây là hình thức tối ưu hóa duy nhất mà các elides (ha!) Quy tắc as-if - elision sao chép có thể được áp dụng ngay cả khi sao chép / di chuyển đối tượng có tác dụng phụ .

Ví dụ sau lấy từ Wikipedia :

struct C {
  C() {}
  C(const C&) { std::cout << "A copy was made.\n"; }
};

C f() {
  return C();
}

int main() {
  std::cout << "Hello World!\n";
  C obj = f();
}

Tùy thuộc vào trình biên dịch & cài đặt, các đầu ra sau đây đều hợp lệ :

Chào thế giới!
Một bản sao đã được thực hiện.
Một bản sao đã được thực hiện.


Chào thế giới!
Một bản sao đã được thực hiện.


Chào thế giới!

Điều này cũng có nghĩa là có thể tạo ra ít đối tượng hơn, vì vậy bạn cũng không thể dựa vào một số hàm hủy cụ thể được gọi. Bạn không nên có logic quan trọng bên trong các công cụ sao chép / di chuyển hoặc hàm hủy, vì bạn không thể dựa vào chúng được gọi.

Nếu một lệnh gọi đến một bản sao hoặc di chuyển hàm tạo bị bỏ qua, hàm tạo đó vẫn phải tồn tại và phải có thể truy cập được. Điều này đảm bảo rằng việc sao chép sao chép không cho phép sao chép các đối tượng không thể sao chép thông thường, ví dụ: vì chúng có một công cụ xây dựng sao chép / di chuyển riêng tư hoặc bị xóa.

C ++ 17 : Kể từ C ++ 17, Copy Elision được đảm bảo khi một đối tượng được trả lại trực tiếp:

struct C {
  C() {}
  C(const C&) { std::cout << "A copy was made.\n"; }
};

C f() {
  return C(); //Definitely performs copy elision
}
C g() {
    C c;
    return c; //Maybe performs copy elision
}

int main() {
  std::cout << "Hello World!\n";
  C obj = f(); //Copy constructor isn't called
}

2
Bạn có thể giải thích khi nào là đầu ra thứ 2 xảy ra và khi thứ 3?
zhangxaochen

3
@zhangxaochen khi nào và làm thế nào trình biên dịch quyết định tối ưu hóa theo cách đó.
Luchian Grigore

10
@zhangxaochen, đầu ra thứ 1: bản sao 1 là từ sự trở lại của một temp và bản sao 2 từ temp đến obj; Thứ 2 là khi một trong những điều trên được tối ưu hóa, có lẽ bản sao reutnr bị xóa bỏ; cả hai đều bị loại bỏ
người chiến thắng

2
Hmm, nhưng theo tôi, đây PHẢI là một tính năng chúng ta có thể dựa vào. Bởi vì nếu chúng ta không thể, nó sẽ ảnh hưởng nghiêm trọng đến cách chúng ta thực hiện các chức năng của mình trong C ++ hiện đại (RVO vs std :: move). Trong khi xem một số video của CppCon 2014, tôi thực sự có ấn tượng rằng tất cả các trình biên dịch hiện đại luôn làm RVO. Hơn nữa, tôi đã đọc ở đâu đó mà không có bất kỳ tối ưu hóa nào, trình biên dịch áp dụng nó. Nhưng, tất nhiên, tôi không chắc chắn về nó. Đó là lý do tại sao tôi hỏi.
j00hi

8
@ j00hi: Không bao giờ viết di chuyển trong câu lệnh return - nếu rvo không được áp dụng, giá trị trả về sẽ được chuyển ra theo mặc định.
MikeMB 10/03/2015

96

Tiêu chuẩn tham khảo

Đối với một cái nhìn ít kỹ thuật và giới thiệu - bỏ qua câu trả lời này .

Đối với các trường hợp phổ biến khi xảy ra cuộc bầu cử sao chép - bỏ qua câu trả lời này .

Sao chép bản sao được xác định trong tiêu chuẩn trong:

12.8 Sao chép và di chuyển các đối tượng lớp [class.copy]

như

31) Khi các tiêu chí nhất định được đáp ứng, việc triển khai được phép bỏ qua việc xây dựng sao chép / di chuyển của một đối tượng lớp, ngay cả khi công cụ sao chép / di chuyển và / hoặc hàm hủy cho đối tượng có tác dụng phụ. Trong các trường hợp như vậy, việc triển khai xử lý nguồn và đích của hoạt động sao chép / di chuyển bị bỏ qua chỉ đơn giản là hai cách khác nhau để đề cập đến cùng một đối tượng và việc phá hủy đối tượng đó xảy ra vào thời điểm sau đó khi hai đối tượng sẽ xảy ra phá hủy mà không tối ưu hóa. 123 Sự bầu chọn này của các hoạt động sao chép / di chuyển, được gọi là cuộc bầu cử sao chép , được cho phép trong các trường hợp sau (có thể được kết hợp để loại bỏ nhiều bản sao):

- trong câu lệnh return trong hàm có kiểu trả về lớp, khi biểu thức là tên của một đối tượng tự động không bay hơi (khác với tham số hàm hoặc mệnh đề bắt) với cùng loại cvunqualified như kiểu trả về hàm, hoạt động sao chép / di chuyển có thể được bỏ qua bằng cách xây dựng đối tượng tự động trực tiếp vào giá trị trả về của hàm

- trong một biểu thức ném, khi toán hạng là tên của một đối tượng tự động không bay hơi (không phải là tham số hàm hoặc mệnh đề bắt) có phạm vi không vượt quá phần cuối của khối thử bao quanh trong cùng (nếu có một), có thể bỏ qua thao tác sao chép / di chuyển từ toán hạng sang đối tượng ngoại lệ (15.1) bằng cách xây dựng đối tượng tự động trực tiếp vào đối tượng ngoại lệ

- khi một đối tượng lớp tạm thời không bị ràng buộc với tham chiếu (12.2) sẽ được sao chép / di chuyển sang một đối tượng lớp có cùng loại cv không đủ tiêu chuẩn, có thể bỏ qua thao tác sao chép / di chuyển bằng cách xây dựng đối tượng tạm thời trực tiếp vào mục tiêu của bản sao bị bỏ qua / di chuyển

- khi khai báo ngoại lệ của trình xử lý ngoại lệ (Điều 15) khai báo một đối tượng cùng loại (ngoại trừ tiêu chuẩn cv) là đối tượng ngoại lệ (15.1), có thể bỏ qua thao tác sao chép / di chuyển bằng cách xử lý khai báo ngoại lệ như một bí danh cho đối tượng ngoại lệ nếu ý nghĩa của chương trình sẽ không thay đổi ngoại trừ việc thực thi các hàm tạo và hàm hủy đối với đối tượng được khai báo bởi khai báo ngoại lệ.

123) Bởi vì chỉ có một đối tượng bị phá hủy thay vì hai và một hàm tạo sao chép / di chuyển không được thực thi, vẫn còn một đối tượng bị phá hủy cho mỗi đối tượng được xây dựng.

Ví dụ được đưa ra là:

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
Thing f() {
  Thing t;
  return t;
}
Thing t2 = f();

và giải thích:

Ở đây, các tiêu chí cho elision có thể được kết hợp để loại bỏ hai lệnh gọi đến hàm tạo sao chép của lớp Thing: sao chép đối tượng tự động cục bộ tvào đối tượng tạm thời cho giá trị trả về của hàm f() và sao chép đối tượng tạm thời đó vào đối tượng t2. Thực tế, việc xây dựng đối tượng cục bộ t có thể được xem là khởi tạo trực tiếp đối tượng toàn cầu t2và sự phá hủy của đối tượng đó sẽ xảy ra khi thoát khỏi chương trình. Thêm một hàm tạo di chuyển vào Thing có tác dụng tương tự, nhưng đó là việc xây dựng di chuyển từ đối tượng tạm thời sang t2đó bị loại bỏ.


1
Đó là từ tiêu chuẩn C ++ 17 hay từ phiên bản cũ hơn?
Nils

90

Các hình thức phổ biến của cuộc bầu cử sao chép

Để biết tổng quan về kỹ thuật - bỏ qua câu trả lời này .

Đối với một cái nhìn ít kỹ thuật và giới thiệu - bỏ qua câu trả lời này .

(Được đặt tên) Tối ưu hóa giá trị trả về là một hình thức phổ biến của bản sao. Nó đề cập đến tình huống mà một đối tượng được trả về bởi giá trị từ một phương thức có bản sao của nó được tách ra. Ví dụ được nêu trong tiêu chuẩn minh họa tối ưu hóa giá trị trả về có tên , vì đối tượng được đặt tên.

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
Thing f() {
  Thing t;
  return t;
}
Thing t2 = f();

Tối ưu hóa giá trị trả lại thường xuyên xảy ra khi trả lại tạm thời:

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
Thing f() {
  return Thing();
}
Thing t2 = f();

Các địa điểm phổ biến khác nơi diễn ra cuộc bầu cử sao chép là khi giá trị tạm thời được thông qua :

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
void foo(Thing t);

foo(Thing());

hoặc khi một ngoại lệ được ném và bắt bởi giá trị :

struct Thing{
  Thing();
  Thing(const Thing&);
};

void foo() {
  Thing c;
  throw c;
}

int main() {
  try {
    foo();
  }
  catch(Thing c) {  
  }             
}

Những hạn chế phổ biến của cuộc bầu cử sao chép là:

  • nhiều điểm trở lại
  • khởi tạo có điều kiện

Hầu hết các trình biên dịch cấp thương mại đều hỗ trợ elision sao chép & (N) RVO (tùy thuộc vào cài đặt tối ưu hóa).


4
Tôi muốn thấy các điểm đạn "Giới hạn chung" được giải thích một chút ... điều gì tạo nên những yếu tố hạn chế này?
phonetagger

@phonetagger Tôi liên kết với bài viết msDN, hy vọng rằng sẽ xóa một số thứ.
Luchian Grigore

54

Copy elision là một kỹ thuật tối ưu hóa trình biên dịch giúp loại bỏ việc sao chép / di chuyển không cần thiết của các đối tượng.

Trong các trường hợp sau đây, trình biên dịch được phép bỏ qua các hoạt động sao chép / di chuyển và do đó không được gọi hàm tạo có liên quan:

  1. NRVO (Tối ưu hóa giá trị trả về được đặt tên) : Nếu một hàm trả về một loại lớp theo giá trị và biểu thức của câu lệnh return là tên của một đối tượng không bay hơi với thời lượng lưu trữ tự động (không phải là tham số hàm), thì sao chép / di chuyển điều đó sẽ được thực hiện bởi trình biên dịch không tối ưu hóa có thể được bỏ qua. Nếu vậy, giá trị trả về được xây dựng trực tiếp trong bộ lưu trữ mà giá trị trả về của hàm sẽ được di chuyển hoặc sao chép.
  2. RVO (Tối ưu hóa giá trị trả về) : Nếu hàm trả về một đối tượng tạm thời không tên sẽ được di chuyển hoặc sao chép vào đích bằng trình biên dịch ngây thơ, có thể bỏ qua bản sao hoặc di chuyển theo 1.
#include <iostream>  
using namespace std;

class ABC  
{  
public:   
    const char *a;  
    ABC()  
     { cout<<"Constructor"<<endl; }  
    ABC(const char *ptr)  
     { cout<<"Constructor"<<endl; }  
    ABC(ABC  &obj)  
     { cout<<"copy constructor"<<endl;}  
    ABC(ABC&& obj)  
    { cout<<"Move constructor"<<endl; }  
    ~ABC()  
    { cout<<"Destructor"<<endl; }  
};

ABC fun123()  
{ ABC obj; return obj; }  

ABC xyz123()  
{  return ABC(); }  

int main()  
{  
    ABC abc;  
    ABC obj1(fun123());//NRVO  
    ABC obj2(xyz123());//NRVO  
    ABC xyz = "Stack Overflow";//RVO  
    return 0;  
}

**Output without -fno-elide-constructors**  
root@ajay-PC:/home/ajay/c++# ./a.out   
Constructor    
Constructor  
Constructor  
Constructor  
Destructor  
Destructor  
Destructor  
Destructor  

**Output with -fno-elide-constructors**  
root@ajay-PC:/home/ajay/c++# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors    
root@ajay-PC:/home/ajay/c++# ./a.out   
Constructor  
Constructor  
Move constructor  
Destructor  
Move constructor  
Destructor  
Constructor  
Move constructor  
Destructor  
Move constructor  
Destructor  
Constructor  
Move constructor  
Destructor  
Destructor  
Destructor  
Destructor  
Destructor  

Ngay cả khi cuộc bầu chọn sao chép diễn ra và copy- / move-constructor không được gọi, nó vẫn phải có mặt và có thể truy cập (như thể không có sự tối ưu hóa nào xảy ra), nếu không thì chương trình không được định dạng.

Bạn chỉ nên cho phép bản sao như vậy ở những nơi không ảnh hưởng đến hành vi có thể quan sát được của phần mềm của bạn. Sao chép bản sao là hình thức tối ưu hóa duy nhất được phép có (nghĩa là elide) tác dụng phụ có thể quan sát được. Thí dụ:

#include <iostream>     
int n = 0;    
class ABC     
{  public:  
 ABC(int) {}    
 ABC(const ABC& a) { ++n; } // the copy constructor has a visible side effect    
};                     // it modifies an object with static storage duration    

int main()   
{  
  ABC c1(21); // direct-initialization, calls C::C(42)  
  ABC c2 = ABC(21); // copy-initialization, calls C::C( C(42) )  

  std::cout << n << std::endl; // prints 0 if the copy was elided, 1 otherwise
  return 0;  
}

Output without -fno-elide-constructors  
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp  
root@ajay-PC:/home/ayadav# ./a.out   
0

Output with -fno-elide-constructors  
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors  
root@ajay-PC:/home/ayadav# ./a.out   
1

GCC cung cấp -fno-elide-constructorstùy chọn để vô hiệu hóa bản sao. Nếu bạn muốn tránh cuộc bầu cử sao chép có thể, sử dụng -fno-elide-constructors.

Bây giờ hầu như tất cả các trình biên dịch đều cung cấp chức năng sao chép khi tối ưu hóa được bật (và nếu không có tùy chọn nào khác được đặt để tắt nó).

Phần kết luận

Với mỗi lần sao chép, một cấu trúc và một lần hủy bản sao phù hợp sẽ bị bỏ qua, do đó tiết kiệm thời gian của CPU và một đối tượng không được tạo, do đó tiết kiệm không gian trên khung ngăn xếp.


6
tuyên bố ABC obj2(xyz123());là NRVO hay RVO? có phải nó không nhận được biến / đối tượng tạm thời giống như ABC xyz = "Stack Overflow";//RVO
Asif Mushtaq

3
Để có một minh họa cụ thể hơn về RVO, bạn có thể tham khảo cụm mà trình biên dịch tạo ra (thay đổi cờ trình biên dịch -fno-elide-constructor để xem diff). godbolt.org/g/Y2KcdH
Gab
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.