2013-03-28 29 views
38

Hãy lấy phương pháp sau đây là một ví dụ:chỉnh việc sử dụng tài liệu tham khảo rvalue như thông số

void Asset::Load(const std::string& Path) 
{ 
    // complicated method.... 
} 

sử dụng chung của phương pháp này sẽ như sau:

Asset ExampleAsset; 
ExampleAsset.Load("image0.png"); 

Vì chúng ta biết hầu hết thời gian Đường dẫn là một giá trị tạm thời, nó có ý nghĩa để thêm một phiên bản Rvalue của phương pháp này? Và nếu vậy, đây có phải là một sự thực hiện đúng hay không;

void Asset::Load(const std::string& Path) 
{ 
    // complicated method.... 
} 
void Asset::Load(std::string&& Path) 
{ 
    Load(Path); // call the above method 
} 

Đây có phải là cách tiếp cận đúng để viết các phiên bản phương pháp rvalue không?

Trả lời

47

Đối với trường hợp cụ thể của bạn, quá tải thứ hai là vô ích.

Với mã gốc, chỉ có một lần quá tải cho Load, chức năng này được gọi cho giá trị và giá trị.

Với mã mới, quá tải đầu tiên được gọi cho giá trị và giá trị thứ hai được gọi cho giá trị. Tuy nhiên, quá tải thứ hai gọi là lần đầu tiên. Cuối cùng, hiệu quả của việc gọi một hoặc cái khác ngụ ý rằng cùng một hoạt động (bất kể việc quá tải đầu tiên nào) sẽ được thực hiện.

Do đó, hiệu ứng của mã gốc và mã mới giống nhau nhưng mã đầu tiên chỉ đơn giản hơn.

Quyết định xem một hàm có phải lấy một đối số theo giá trị, tham chiếu lvalue hoặc tham chiếu rvalue hay không phụ thuộc rất nhiều vào những gì nó thực hiện. Bạn nên cung cấp quá tải tham chiếu rvalue khi bạn muốn di chuyển đối số đã truyền. Có một số tốt references trên di chuyển semantincs ra khỏi đó, vì vậy tôi sẽ không bao gồm nó ở đây.

Bonus:

Để giúp tôi làm cho quan điểm của tôi xem xét probe lớp này đơn giản:

struct probe { 
    probe(const char* ) { std::cout << "ctr " << std::endl; } 
    probe(const probe&) { std::cout << "copy" << std::endl; } 
    probe(probe&&  ) { std::cout << "move" << std::endl; } 
}; 

Bây giờ xem xét chức năng này:

void f(const probe& p) { 
    probe q(p); 
    // use q; 
} 

Calling f("foo"); xuất ra như sau:

ctr 
copy 

Không có bất ngờ nào ở đây: chúng tôi tạo tạm thời probe chuyển số const char*"foo". Do đó dòng đầu ra đầu tiên. Sau đó, tạm thời này được liên kết với p và một bản sao q của p được tạo bên trong f. Do đó dòng đầu ra thứ hai.

Bây giờ, hãy xem xét việc p theo giá trị, nghĩa là, thay đổi f tới:

void f(probe p) { 
    // use p; 
} 

Kết quả của f("foo"); tại

ctr 

là Một số sẽ ngạc nhiên rằng trong trường hợp này: không có sao chép! Nói chung, nếu bạn lấy một đối số bằng cách tham chiếu và sao chép nó bên trong hàm của bạn, thì tốt hơn là lấy đối số theo giá trị. Trong trường hợp này, thay vì tạo tạm thời và sao chép nó, trình biên dịch có thể xây dựng đối số (p trong trường hợp này) trực tiếp từ đầu vào ("foo"). Để biết thêm thông tin, xem Want Speed? Pass by Value. bởi Dave Abrahams.

Có hai ngoại lệ đáng chú ý cho hướng dẫn này: nhà thầu và nhà điều hành chuyển nhượng.

xem xét lớp này:

struct foo { 
    probe p; 
    foo(const probe& q) : p(q) { } 
}; 

Các nhà xây dựng phải mất một probe bằng cách tham chiếu const và sau đó sao chép nó vào p. Trong trường hợp này, theo hướng dẫn ở trên không mang lại bất kỳ cải thiện hiệu suất nào và hàm tạo bản sao của probe sẽ được gọi là anyway. Tuy nhiên, việc lấy q theo giá trị có thể tạo ra một vấn đề về độ phân giải quá tải tương tự như vấn đề với toán tử gán mà bây giờ tôi sẽ đề cập đến.

Giả sử rằng lớp học của chúng tôi probe có phương pháp không ném swap. Sau đó, việc thực hiện đề nghị của toán tử gán của nó (suy nghĩ trong C++ 03 thuật ngữ trong thời gian tới) được

probe& operator =(const probe& other) { 
    probe tmp(other); 
    swap(tmp); 
    return *this; 
} 

Sau đó, theo phương châm trên, nó tốt hơn để viết nó như thế này

probe& operator =(probe tmp) { 
    swap(tmp); 
    return *this; 
} 

Bây giờ, hãy nhập C++ 11 với tham chiếu rvalue và di chuyển ngữ nghĩa. Bạn đã quyết định thêm toán tử chuyển nhượng di chuyển:

probe& operator =(probe&&); 

Bây giờ hãy gọi toán tử gán tạm thời tạo ra sự mơ hồ vì cả quá tải đều khả thi và không được ưu tiên hơn. Để giải quyết vấn đề này, sử dụng thực hiện ban đầu của toán tử gán (lấy đối số bằng tham chiếu const).

Thực ra, vấn đề này không dành riêng cho các nhà xây dựng và nhà điều hành chuyển nhượng và có thể xảy ra với bất kỳ chức năng nào. (Đó là nhiều khả năng là bạn sẽ trải nghiệm nó với nhà thầu và các toán tử gán mặc dù.) Ví dụ, gọi g("foo"); khi g đã sau hai quá tải làm tăng sự nhập nhằng:

void g(probe); 
void g(probe&&); 
+0

'Để giải quyết vấn đề này, hãy sử dụng thực thi gốc của toán tử gán (lấy đối số bằng tham chiếu const) .' Thay vào đó, chỉ cần áp dụng thành phần sao chép và trao đổi chung bằng cách thực hiện một hàm khởi động di chuyển và để trình biên dịch quyết định sao chép - hoặc di chuyển - xây dựng biến 'tmp' cho toán tử gán. – SebNag

7

Trừ khi bạn đang làm điều gì khác ngoài gọi phiên bản tham chiếu lvalue của Load, bạn không cần hàm thứ hai, vì giá trị sẽ liên kết với tham chiếu const lvalue.

+0

Đây có phải là luôn luôn như vậy cho các thông số tham chiếu const? Điều gì về tham chiếu không const? – Grapes

+0

Tôi đoán rvalue sẽ không liên kết với tham chiếu lvalue "non-const". Phải là một lỗi? – Arun

+0

@Arun: Có, bạn nói đúng: một giá trị không hợp lệ ràng buộc với một tham chiếu không phải lvalue. Cố gắng làm điều này làm tăng một lỗi. –

1

Nó thường là một câu hỏi liệu trong nội bộ bạn sẽ làm một bản sao (rõ ràng, hoặc ngầm) của đối tượng gửi đến (cung cấp đối số T&&), hoặc bạn sẽ chỉ sử dụng nó (dính vào [const] T&).

+2

... cái gì? Đó hoàn toàn không phải là ý nghĩa được chấp nhận của một tham chiếu rvalue. Tham chiếu Rvalue được sử dụng để cho biết người nhận có ** di chuyển **/pilfer/nếu không hiển thị trơ (nhưng hợp lệ) tham chiếu, ** không ** sao chép chúng. Sao chép ngữ nghĩa phải được biểu thị bằng 'const &' hoặc giá trị. –

1

Nếu hàm Load thành viên của bạn không chỉ định từ chuỗi đến, bạn chỉ cần cung cấp void Asset::Load(const std::string& Path).

Nếu bạn chỉ định từ số path đến, hãy nói đến biến thành viên, sau đó có một trường hợp có thể hiệu quả hơn một chút để cung cấp void Asset::Load(std::string&& Path), nhưng bạn cần triển khai khác để gán ala loaded_from_path_ = std::move(Path);.

Lợi ích tiềm năng là cho người gọi, trong đó có phiên bản && họ có thể nhận được các khu vực tự do cửa hàng đã được sở hữu bởi các biến thành viên, tránh bi quan delete[] ion của bộ đệm mà bên void Asset::Load(const std::string& Path) và khả năng tái phân bổ thời gian tới chuỗi của người gọi được gán cho (giả sử bộ đệm đủ lớn để vừa với giá trị tiếp theo của nó).

Trong trường hợp đã nêu của bạn, bạn thường chuyển các chuỗi ký tự chuỗi; người gọi như vậy sẽ không nhận được lợi ích nào từ bất kỳ quá tải && vì không có người gọi std::string phiên bản nào để nhận bộ đệm của thành viên dữ liệu hiện có.

2

Vì chúng ta biết phần lớn thời gian Đường dẫn là một giá trị tạm thời, bạn có nên thêm phiên bản Rvalue của phương pháp này không?

Có lẽ không ... Trừ khi bạn cần phải làm điều gì đó phức tạp bên trong Load() yêu cầu tham số không phải const. Ví dụ: có thể bạn muốn std::move(Path) vào một chuỗi khác. Trong trường hợp đó nó có thể có ý nghĩa để sử dụng ngữ nghĩa di chuyển.

Đây có phải là cách tiếp cận đúng để viết các phiên bản phương pháp rvalue không?

Không, bạn nên làm điều đó theo cách khác xung quanh:

void Asset::load(const std::string& path) 
{ 
    auto path_copy = path; 
    load(std::move(path_copy)); // call the below method 
} 
void Asset::load(std::string&& path) 
{ 
    // complicated method.... 
} 
Các vấn đề liên quan