2010-01-09 29 views
6

Gần đây tôi đã đọc (và không may quên ở đâu), rằng cách tốt nhất để viết operator = là như thế này:constructor sao chép rõ ràng hoặc tham số ngầm theo giá trị

foo &operator=(foo other) 
{ 
    swap(*this, other); 
    return *this; 
} 

thay vì điều này:

foo &operator=(const foo &other) 
{ 
    foo copy(other); 
    swap(*this, copy); 
    return *this; 
} 

Ý tưởng là nếu toán tử = được gọi với giá trị rvalue, phiên bản đầu tiên có thể tối ưu hóa việc xây dựng bản sao. Vì vậy, khi được gọi với một rvalue, phiên bản đầu tiên là nhanh hơn và khi được gọi với một lvalue hai là tương đương.

Tôi tò mò về những gì người khác nghĩ về điều này? Mọi người có nên tránh phiên bản đầu tiên vì thiếu nhân chứng không? Tôi có đúng là phiên bản đầu tiên có thể tốt hơn và không bao giờ có thể tồi tệ hơn không?

+0

'Trao đổi' là gì? Nếu nó là 'foo temp = x; x = y; y = temp; 'bạn có sự đệ quy infinte của hàm' operator = 'và' swap'. –

+1

Tôi đã viết một chương trình để kiểm tra lý thuyết của @Alexey Malistov và ông ấy đã đúng - tôi đã có đệ quy vô hạn. – nobar

+0

Bất kỳ lớp nào thực hiện một bản sao và thành phần hoán đổi không thể dựa vào việc thực thi 'std :: swap' mặc định trong toán tử gán bản sao của nó. Điều đó khá nhiều mà không nói. –

Trả lời

4

Bạn có thể đọc nó từ: http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

Tôi không có nhiều điều để nói vì tôi nghĩ rằng liên kết giải thích lý do căn bản khá tốt. Tôi có thể xác nhận rằng biểu mẫu đầu tiên tạo ra ít bản sao hơn trong các bản dựng của tôi với MSVC, điều này có ý nghĩa vì các trình biên dịch có thể không thực hiện được phép sao chép trên biểu mẫu thứ hai. Tôi đồng ý rằng hình thức đầu tiên là một cải tiến nghiêm ngặt và không bao giờ tồi tệ hơn lần thứ hai.

Chỉnh sửa: Biểu mẫu đầu tiên có thể ít thành ngữ hơn một chút, nhưng tôi không nghĩ nó ít rõ ràng hơn nhiều. (IMO, không có gì đáng ngạc nhiên hơn khi nhìn thấy việc thực hiện sao chép và hoán đổi của toán tử gán lần đầu tiên.)

Chỉnh sửa # 2: Rất tiếc, tôi muốn ghi chép sao chép, chứ không phải RVO.

+1

Tôi không đồng ý với thành ngữ đầu tiên là thành ngữ. Đây là phiên bản ít phổ biến hơn. Tôi nghĩ nếu bạn làm việc cho một công ty lớn thực hiện rất nhiều đánh giá mã, bạn sẽ thấy mình bị gửi trở lại làm theo cách thông thường. Bình thường là tốt hơn cho bảo trì như mọi người không cần phải làm một đôi để làm việc ra những gì đang xảy ra. Tôi nghĩ rằng phiên bản đầu tiên chỉ là một mẹo bên tốt đẹp để cho thấy bạn cool như thế nào. –

+1

Tôi đã không hoàn toàn nói rằng hình thức đầu tiên là thành ngữ (và tôi đã nói rằng hình thức thứ hai là nhiều hơn như vậy). Nhưng thành ngữ phải bắt đầu ở đâu đó, và tôi biết hình thức thứ hai khiến tôi làm một cú đúp lần đầu tiên tôi nhìn thấy nó. – jamesdlin

+1

@Martin: như Herb Sutter đã nói, khi hội nghị xung đột với thực hành tốt, chúng ta phải tự hỏi liệu chúng ta nên loại bỏ quy ước ủng hộ thực hành tốt, hoặc loại bỏ thực hành tốt ủng hộ quy ước. –

-3

Tôi nghĩ rằng bạn có thể khó hiểu sự khác biệt giữa:

foo &operator=(const foo &other);
const foo &operator=(const foo &other);

Các hình thức đầu tiên nên được sử dụng để cho phép: (a = b) = c;

+4

Tôi không nghĩ anh ta đang bối rối - câu hỏi của anh ấy có ý nghĩa hoàn hảo. –

-2

Hai là thực sự như vậy. Sự khác biệt duy nhất là nơi bạn nhấn "Step In" trong trình gỡ lỗi. Và bạn nên biết trước nơi để làm điều đó.

2

Tôi thường thích vị trí thứ hai từ quan điểm dễ đọc và 'ít ngạc nhiên', tuy nhiên, tôi thừa nhận rằng điểm đầu tiên có thể hiệu quả hơn khi thông số tạm thời.

Điều đầu tiên thực sự có thể dẫn đến không bản sao, không chỉ là bản sao duy nhất và có thể hiểu rằng đây có thể là mối quan tâm thực sự trong các tình huống khắc nghiệt.

Ví dụ: Thực hiện chương trình thử nghiệm này. gcc -O3 -S (phiên bản gcc 4.4.2 20091222 (Red Hat 4.4.2-20) (GCC)) tạo một cuộc gọi đến hàm tạo bản sao của B nhưng không có cuộc gọi đến hàm tạo bản sao của A cho hàm f (toán tử gán được inline cho cả AB). AB cả hai có thể được lấy là các lớp chuỗi rất cơ bản. Phân bổ và sao chép cho data sẽ xảy ra trong các nhà xây dựng và deallocation trong destructor.

#include <algorithm> 

class A 
{ 
public: 
    explicit A(const char*); 
    A& operator=(A val)  { swap(val); return *this; } 
    void swap(A& other)  { std::swap(data, other.data); } 
    A(const A&); 
    ~A(); 

private: 
    const char* data; 
}; 

class B 
{ 
public: 
    explicit B(const char*); 
    B& operator=(const B& val) { B tmp(val); swap(tmp); return *this; } 
    void swap(B& other)   { std::swap(data, other.data); } 
    B(const B&); 
    ~B(); 

private: 
    const char* data; 
}; 

void f(A& a, B& b) 
{ 
    a = A("Hello"); 
    b = B("World"); 
} 
+0

Điều đáng chú ý là cả lớp học đều không có thành viên dữ liệu. Ngoài ra, bạn có thể chỉnh sửa ví dụ của mình để giảm thiểu không gian trắng dọc không? –

+0

@Neil: Các thành viên dữ liệu không tạo ra sự khác biệt nào, tôi chỉ thêm một 'dữ liệu dài [32];' vào cả hai cấu trúc trong khai thác thử nghiệm của tôi. Về lý thuyết, các thành viên dữ liệu có thể được xúc động bởi các nhà xây dựng mà tôi đã để lại không xác định. Các cuộc gọi được tạo ra cho các nhà xây dựng vẫn như cũ, một cuộc gọi đến 'B :: B (const B &)', không gọi đến 'A :: A (const A &) '. –

+0

Ah, tôi nghĩ tôi hiểu ý của bạn là gì. Tôi đã thêm một thành viên dữ liệu khác của loại lớp (lớp C) với hàm tạo đã khai báo nhưng không xác định, sao chép hàm tạo, hàm hủy và thành phần dữ liệu void *. Mặc dù đã có thêm một số mã xử lý ngoại lệ được tạo ra, và một số lệnh gọi tới 'C :: ~ C()' ở các vị trí mong đợi, sự khác biệt giữa việc gọi 'B :: B (const B &)' và 'A :: A (const A &) '. Cho dù điều này làm cho bất kỳ sự khác biệt thế giới thực là mở để tranh luận, nhưng khả năng chắc chắn là có. –

0

Với này

foo &foo::operator=(foo other) {/*...*/ return *this;} 
foo f(); 

trong mã như thế này

foo bar; 
bar = f(); 

nó có thể được dễ dàng hơn cho một trình biên dịch để loại bỏ các cuộc gọi đến constructor sao chép. Với RVO, nó có thể sử dụng địa chỉ của tham số other của toán tử làm vị trí cho f() để tạo giá trị trả về của nó tại.

Dường như tối ưu hóa này cũng có thể xảy ra đối với trường hợp thứ hai, mặc dù tôi tin rằng điều đó có thể khó hơn. (Đặc biệt khi người vận hành không được gạch chân.)

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