2010-01-26 39 views
8

Tôi đã thấy nhiều đối số sử dụng giá trị trả lại thích hợp hơn với các tham số ngoài. Tôi bị thuyết phục về lý do tại sao để tránh chúng, nhưng tôi thấy mình không chắc chắn nếu tôi đang chạy vào trường hợp mà nó là không thể tránh khỏi.Làm thế nào để tránh các tham số?

Phần một câu hỏi của tôi là: Một số cách yêu thích/phổ biến của bạn khi sử dụng tham số ngoài là gì? Các công cụ dọc theo các dòng: Man, trong đánh giá ngang hàng Tôi luôn thấy các lập trình viên khác làm điều này khi họ có thể dễ dàng thực hiện nó theo cách này.

Phần hai giao dịch câu hỏi của tôi với một số trường hợp cụ thể mà tôi gặp phải nơi tôi muốn tránh tham số ngoài nhưng không thể nghĩ ra cách làm sạch.

Ví dụ 1: Tôi có một lớp học với bản sao đắt tiền mà tôi muốn tránh. Công việc có thể được thực hiện trên đối tượng và điều này xây dựng lên các đối tượng được đắt tiền để sao chép. Công việc xây dựng dữ liệu không phải là chính xác tầm thường. Hiện tại, tôi sẽ truyền đối tượng này vào một hàm sẽ sửa đổi trạng thái của đối tượng. Điều này với tôi là thích hợp hơn để new'ing đối tượng nội bộ để các chức năng công nhân và trả lại nó trở lại, vì nó cho phép tôi để giữ cho mọi thứ trên stack. đang

class ExpensiveCopy //Defines some interface I can't change. 
{ 
public: 
    ExpensiveCopy(const ExpensiveCopy toCopy){ /*Ouch! This hurts.*/ }; 
    ExpensiveCopy& operator=(const ExpensiveCopy& toCopy){/*Ouch! This hurts.*/}; 

    void addToData(SomeData); 
    SomeData getData(); 
} 

class B 
{ 
public: 
    static void doWork(ExpensiveCopy& ec_out, int someParam); 
    //or 
    // Your Function Here. 
} 

Sử dụng chức năng của tôi, tôi nhận được gọi như thế này:

const int SOME_PARAM = 5; 
ExpensiveCopy toModify; 
B::doWork(toModify, SOME_PARAM); 

Tôi muốn có một cái gì đó như thế này:

ExpensiveCopy theResult = B::doWork(SOME_PARAM); 

Nhưng tôi không biết nếu điều này có khả năng.

Ví dụ thứ hai: Tôi có một mảng đối tượng. Các đối tượng trong mảng là một kiểu phức tạp, và tôi cần phải làm việc trên từng phần tử, làm việc mà tôi muốn tách biệt khỏi vòng lặp chính truy cập từng phần tử. Mã hiện tại trông giống như sau:

std::vector<ComplexType> theCollection; 
for(int index = 0; index < theCollection.size(); ++index) 
{ 
    doWork(theCollection[index]); 
} 

void doWork(ComplexType& ct_out) 
{ 
    //Do work on the individual element. 
} 

Bất kỳ đề xuất nào về cách xử lý một số trường hợp này? Tôi làm việc chủ yếu trong C++, nhưng tôi quan tâm để xem liệu các ngôn ngữ khác có tạo điều kiện cho việc thiết lập dễ dàng hơn hay không. Tôi đã gặp RVO như một giải pháp có thể, nhưng tôi cần phải đọc thêm về nó và nó có vẻ giống như một tính năng cụ thể của trình biên dịch.

+0

Ngữ nghĩa của tham số tham chiếu không phải là (có thể) được thay đổi trong hàm. Nếu bạn sẽ không thay đổi nó, bạn sẽ vượt qua nó dưới dạng tham chiếu const. – Bill

+0

* "nhưng tôi quan tâm để xem liệu các ngôn ngữ khác có tạo điều kiện dễ dàng hơn không" * - các ngôn ngữ như Java và C# đơn giản không có vấn đề đó vì chúng luôn tham chiếu đến các đối tượng xung quanh. –

+0

Phản hồi tuyệt vời từ mọi người. Tôi có cảm giác rằng trong các ví dụ cụ thể của tôi rằng sẽ không có nhiều lựa chọn khác.Tôi đã nhìn thấy các bài đăng trên SO từ những người rất kiên quyết về cách họ tránh các thông số bằng mọi giá. Tôi đã hy vọng rằng có lẽ một trong số họ có thể giải thích về cách họ đạt được thành tích như vậy. Đối với RVO, tôi hơi do dự khi sử dụng các tính năng cụ thể của trình biên dịch, vì sẽ khó giải thích cho các ông chủ của tôi tại sao tôi cần phải thiết kế lại một nửa mã của tôi vì nó không còn hoạt động nữa do thiếu tính năng thay đổi/nâng cấp trình biên dịch. –

Trả lời

7

Tôi không chắc chắn lý do tại sao bạn đang cố gắng tránh chuyển các tham chiếu tại đây. Có khá nhiều tình huống mà các ngữ nghĩa học theo tham chiếu tồn tại.

static void doWork(ExpensiveCopy& ec_out, int someParam); 

trông hoàn toàn tốt đẹp đối với tôi.

Nếu bạn thực sự muốn thay đổi nó sau đó bạn đã có một vài tùy chọn

  1. Move DoWork vì vậy đó là đó là một thành viên của ExpensiveCopy (mà bạn nói rằng bạn không thể làm được, vì vậy đó là ra)
  2. trả lại con trỏ (thông minh) từ doWork thay vì sao chép nó.(Mà bạn không muốn làm như bạn muốn giữ cho mọi thứ trên stack)
  3. Dựa vào RVO (mà những người khác đã chỉ ra được hỗ trợ bởi khá nhiều tất cả các trình biên dịch hiện đại)
+0

Đồng ý. Chỉ có một điều, tùy chọn 1 mà bạn đề cập là không có sẵn, vì OP nói rằng 'ExpensiveCopy' không thể sửa đổi được. – stakx

+0

@stakx, bạn nói đúng, tôi đã bỏ lỡ điều đó. Sẽ sửa lỗi – Glen

+0

@stakx: Tôi biết OP đã đề cập rằng anh ấy không thể sửa đổi lớp học. Nhưng tại sao anh ta không thể phân lớp nó và thêm phương thức doWork() vào đó? – slebetman

1

Trừ khi bạn đang đi xuống "mọi thứ là bất biến" tuyến đường, mà không ngồi quá tốt với C + +. bạn không thể dễ dàng tránh các tham số. Thư viện chuẩn C++ sử dụng chúng, và những gì đủ tốt cho nó là đủ tốt cho tôi.

+0

Tôi hoàn toàn không đồng ý với tuyên bố cuối cùng: không may C++ Standard Library không gần lý tưởng (chỉ từ đỉnh đầu của tôi: std :: string madness, over-elaborated stream, v.v.) Tôi không muốn bắt đầu chiến thắng nhưng tôi đoán nó không phải là ý tưởng tốt nhất để biện minh cho một cái gì đó vì ví dụ nổi tiếng. –

+2

Tất nhiên nó có mụn cóc của nó, nhưng nói chung tôi thấy nó cực kỳ mạnh mẽ và dễ sử dụng - tôi chỉ muốn mã của riêng tôi cũng được thiết kế. –

0

Như ví dụ đầu tiên của bạn: return value optimization thường sẽ cho phép đối tượng được trả lại được tạo trực tiếp tại chỗ, thay vì phải sao chép đối tượng xung quanh. Tất cả các trình biên dịch hiện đại đều làm điều này.

3

Mỗi biên dịch hữu ích không RVO (trở về giá trị tối ưu hóa) nếu tối ưu hóa được kích hoạt, do đó sau một cách hiệu quả không dẫn sao chép:

Expensive work() { 
    // ... no branched returns here 
    return Expensive(foo); 
} 

Expensive e = work(); 

Trong một số trường hợp trình biên dịch có thể áp dụng NRVO, tên là tối ưu hóa giá trị trả về, cũng như:

Expensive work() { 
    Expensive e; // named object 
    // ... no branched returns here 
    return e; // return named object 
} 

Tuy nhiên, điều này không đáng tin cậy, chỉ hoạt động trong các trường hợp nhỏ hơn và phải được kiểm tra. Nếu bạn không muốn thử nghiệm mọi trường hợp, chỉ cần sử dụng các tham số ngoài có tham chiếu trong trường hợp thứ hai.

2

IMO điều đầu tiên bạn nên tự hỏi là liệu sao chép ExpensiveCopy thực sự quá đắt. Và để trả lời điều đó, bạn thường sẽ cần một hồ sơ. Trừ khi một trình thông báo cho bạn biết rằng việc sao chép thực sự là một nút cổ chai, chỉ cần viết mã dễ đọc hơn: ExpensiveCopy obj = doWork(param);.

Tất nhiên, có những trường hợp thực sự không thể sao chép đối tượng vì hiệu suất hoặc các lý do khác. Sau đó, áp dụng Neil's answer.

0

Bạn đang làm việc trên nền tảng nào?

Lý do tôi hỏi là nhiều người đã đề xuất Tối ưu hóa giá trị trả về, đây là một trình tối ưu hóa trình biên dịch rất tiện dụng trong hầu hết mọi trình biên dịch. Ngoài ra, Microsoft và Intel thực hiện những gì họ gọi là Đặt lại giá trị tối ưu hóa mà thậm chí còn tiện dụng hơn.

Trong tiêu chuẩn Trả về giá trị tối ưu, câu lệnh trả về của bạn là một lời gọi đến hàm tạo của đối tượng, cho trình biên dịch loại bỏ các giá trị tạm thời (không nhất thiết là thao tác sao chép).

Trong đặt tên lại giá trị tối ưu hóa bạn có thể trả về một giá trị theo tên của nó và trình biên dịch sẽ làm điều tương tự. Ưu điểm của NRVO là bạn có thể thực hiện các thao tác phức tạp hơn trên giá trị đã tạo (như gọi các hàm trên nó) trước khi trả về nó.

Mặc dù không phải trong số này thực sự loại bỏ một bản sao đắt tiền nếu dữ liệu trả về của bạn là rất lớn, chúng giúp đỡ.

Để tránh việc sao chép, cách thực sự duy nhất để làm điều đó là với con trỏ hoặc tham chiếu vì chức năng của bạn cần phải sửa đổi dữ liệu ở nơi bạn muốn nó kết thúc. Điều đó có nghĩa là bạn có thể muốn có tham số pass-by-reference.

Ngoài ra tôi cho rằng tôi nên chỉ ra rằng việc tham chiếu vượt qua là rất phổ biến trong mã hiệu suất cao vì lý do cụ thể này. Sao chép dữ liệu có thể cực kỳ tốn kém, và nó thường là một cái gì đó mọi người bỏ qua khi tối ưu hóa mã của họ.

2

Ngoài tất cả các bình luận ở đây tôi muốn đề cập đến điều đó trong C++ 0x bạn muốn hiếm khi sử dụng tham số đầu ra cho mục đích tối ưu hóa - vì Move Constructors (xem here)

+0

Tôi thực sự gần đây đã cố gắng thiết lập một hệ thống hỗ trợ đọc tập tin sử dụng di chuyển ngữ nghĩa một lần. Làm việc tuyệt vời, nhưng tôi đã bị đốt cháy cuối cùng do không tương thích với các trình biên dịch khác nhau. Đã phải thay đổi tất cả các tham số đó là một bummer thực sự. –

0

Theo như tôi có thể nhìn thấy , lý do để thích các giá trị trả về cho tham số là nó rõ ràng hơn, và nó hoạt động với lập trình hàm thuần túy (bạn có thể nhận được một số đảm bảo tốt đẹp nếu hàm chỉ phụ thuộc vào tham số đầu vào, trả về giá trị và không có tác dụng phụ). Lý do đầu tiên là phong cách, và theo ý kiến ​​của tôi không phải tất cả những điều quan trọng. Thứ hai không phù hợp với C++. Do đó, tôi sẽ không cố bóp méo bất cứ điều gì để tránh các thông số.

Thực tế đơn giản là một số hàm phải trả về nhiều thứ và trong hầu hết các ngôn ngữ, điều này gợi ý các tham số. Common Lisp có multiple-value-bindmultiple-value-return, trong đó danh sách các biểu tượng được cung cấp bởi liên kết và danh sách các giá trị được trả về. Trong một số trường hợp, một hàm có thể trả về một giá trị tổng hợp, chẳng hạn như danh sách các giá trị sau đó sẽ được giải mã, và nó không phải là một vấn đề lớn đối với hàm C++ để trả về một std::pair. Trả về nhiều hơn hai giá trị theo cách này trong C++ trở nên khó xử. Nó luôn luôn có thể xác định một cấu trúc, nhưng việc xác định và tạo ra nó thường sẽ phức tạp hơn các tham số ngoài.

Trong một số trường hợp, giá trị trả lại bị quá tải. Trong C, getchar() trả về int, với ý tưởng là có nhiều giá trị int hơn char (đúng trong tất cả các triển khai mà tôi biết, sai trong một số tôi có thể dễ dàng tưởng tượng), vì vậy một trong các giá trị có thể được sử dụng để biểu thị kết thúc của tập tin. atoi() trả về một số nguyên, hoặc số nguyên được biểu thị bằng chuỗi được truyền hoặc không nếu không có, vì vậy nó trả về cùng một điều cho "0" và "ếch". (Nếu bạn muốn biết liệu có giá trị int hay không, hãy sử dụng strtol(), có tham số ngoài.)

Luôn có kỹ thuật ném ngoại lệ trong trường hợp lỗi, nhưng không phải tất cả các giá trị trả về nhiều lần là lỗi và không phải tất cả các lỗi đều là ngoại lệ.

Vì vậy, các giá trị trả về quá tải gây ra sự cố, nhiều giá trị trả về không dễ sử dụng ở tất cả các ngôn ngữ và trả về một lần không phải lúc nào cũng tồn tại. Ném một ngoại lệ thường không phù hợp. Sử dụng các tham số ngoài thường là giải pháp sạch nhất.

0

Tự hỏi tại sao bạn có một số phương pháp thực hiện công việc trên đối tượng đắt tiền để sao chép này ngay từ đầu. Giả sử bạn có một cái cây, bạn có thể gửi cây đó vào một phương pháp xây dựng nào đó hay không để cho cây xây dựng phương pháp xây dựng riêng của nó? Các tình huống như thế này xuất hiện liên tục khi bạn có một chút thiết kế nhưng có xu hướng gấp lại vào bản thân khi bạn rơi xuống.

Tôi biết trong thực tế, chúng tôi không phải lúc nào cũng thay đổi mọi đối tượng, nhưng việc chuyển các tham số là hoạt động tác dụng phụ và làm cho việc tìm hiểu điều gì đang diễn ra khó khăn hơn. để làm điều đó (trừ khi bị ép buộc bằng cách làm việc trong các khuôn khổ mã của người khác). Đôi khi nó được dễ dàng hơn, nhưng nó chắc chắn không mong muốn sử dụng nó không có lý do (nếu bạn đã trải qua một vài dự án lớn, nơi luôn luôn có một nửa tá các thông số bạn sẽ biết những gì tôi có ý nghĩa).

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