2011-06-16 43 views
5

Tôi muốn quá tải một hàm để nó thao tác đối số của nó theo một cách nào đó và sau đó trả về tham chiếu đến đối số - nhưng nếu đối số không thể thay đổi được, thì nó sẽ trả về thao tác sao chép của đối số thay thế. Sau khi rối tung xung quanh với nó cho các lứa tuổi, đây là những gì tôi đã đưa ra.Quá tải hàm rvalue

using namespace std; 

string& foo(string &in) 
{ 
    in.insert(0, "hello "); 
    return in; 
} 

string foo(string &&in) 
{ 
    return move(foo(in)); 
} 

string foo(const string& in) 
{ 
    return foo(string(in)); 
} 

Mã này có vẻ hoạt động chính xác nhưng tôi muốn biết liệu có ai có thể nghĩ cách tốt hơn để làm điều đó không.

Dưới đây là một chương trình thử nghiệm:

int main(void) 
{ 
    string var = "world"; 
    const string var2 = "const world"; 
    cout << foo(var) << endl; 
    cout << var << endl; 

    cout << foo(var2) << endl; 
    cout << var2 << endl; 

    cout << foo(var + " and " + var2) << endl; 
    return 0; 
} 

Kết quả đúng là

hello world 
hello world 
hello const world 
const world 
hello hello world and const world 

tôi con số đó sẽ là hơi gọn gàng nếu tôi có thể làm điều này:

string& foo(string &in) 
{ 
    in.insert(0, "hello "); 
    return in; 
} 

string foo(string in) 
{ 
    return move(foo(in)); 
} 

Tất nhiên, không hoạt động vì hầu hết các cuộc gọi hàm đến foo sẽ không rõ ràng - bao gồm cả cuộc gọi trong foo! Nhưng nếu tôi bằng cách nào đó có thể yêu cầu trình biên dịch ưu tiên cho trình biên dịch đầu tiên ...

Như tôi đã nói, mã tôi đã đăng hoạt động chính xác. Điều chính tôi không thích về nó là mã phụ lặp đi lặp lại. Nếu tôi có một loạt các chức năng như thế thì nó sẽ trở thành một mớ hỗn độn, và hầu hết nó sẽ rất lặp đi lặp lại. Vì vậy, như là một phần thứ hai cho câu hỏi của tôi: bất cứ ai có thể nghĩ ra một cách để tự động tạo mã cho các chức năng thứ hai và thứ ba foo? ví dụ:

// implementation of magic_function_overload_generator 
// ??? 

string& foo(string &in); 
magic_function_overload_generator<foo>; 

string& bar(string &in); 
magic_function_overload_generator<bar>; 

// etc 
+7

Điều này nghe có vẻ đáng sợ. Tùy thuộc vào loại tôi chuyển tới hàm, trạng thái kết quả của giá trị trả về * và tham số * có thể hoàn toàn khác. Đó chỉ là yêu cầu cho các lỗi tinh tế. Tại sao không cho phép người dùng quyết định liệu họ có muốn sửa đổi đối tượng tại chỗ hay trả lại một bản sao bằng cách gọi một cách rõ ràng các chức năng khác nhau không? – jalf

+0

Nó không có vẻ đặc biệt đáng sợ với tôi, nhưng có thể bạn đã đúng. Cách tôi nghĩ về nó là chức năng thay đổi đầu vào bất cứ khi nào nó có thể; nhưng nếu nó không thể ... sau đó nó không, nhưng nó vẫn mang lại giá trị trả về đúng. Loại điều tôi có thể sử dụng nó là một cái gì đó giống như chức năng "Dấu chấm câu", một chuỗi có dấu chấm câu kém và sửa lỗi. Bạn có thể muốn gửi kết quả trực tiếp tới cout, hoặc bạn có thể muốn thực hiện một số thao tác khác trên chuỗi sau đó. Vì vậy, đôi khi bạn có thể vượt qua các giá trị không đổi, và đôi khi ... bạn cũng có được ý tưởng. – karadoc

+1

nhưng quan điểm của tôi là dù hoặc một hay khác xảy ra phụ thuộc vào những gì lập trình viên muốn, nhưng trên một số chi tiết ngữ nghĩa tương đối tinh tế (là loại đối số const hay không? Có phải là một giá trị hay không?), có thể dễ dàng thay đổi theo thời gian mà không có lập trình viên đưa ra quyết định rằng "bây giờ tôi muốn trả lại một bản sao, thay vì sửa đổi đối tượng tại chỗ". Tôi hiểu những gì bạn đang cố gắng làm, nhưng đó là một quyết định mà người lập trình có thể dễ dàng thực hiện và nơi thư viện của bạn đưa ra dự đoán sai có thể có hậu quả rất xấu. – jalf

Trả lời

4

tôi sẽ thoát khỏi các tài liệu tham khảo tất cả với nhau và chỉ cần viết một chức năng mà đi và trả về giá trị:

std::string foo(std::string in) 
{ 
    in.insert(0, "hello "); 
    return in; 
} 

Nếu bạn vượt qua một giá trị trái, chuỗi đầu vào sẽ được sao chép. Nếu bạn vượt qua một rvalue, nó sẽ được di chuyển.

Khi rời khỏi hàm, đặt tên tối ưu hóa giá trị trả về có thể sẽ khởi động, do đó, lợi nhuận về cơ bản là không có op. Nếu trình biên dịch quyết định chống lại điều đó, kết quả sẽ được di chuyển (mặc dù in là một lvalue).

Điều tốt về tham chiếu rvalue là bạn phải suy nghĩ ít hơn về nơi đặt tham chiếu trong mã người dùng để đạt được hiệu quả. Với các kiểu di chuyển, giá trị truyền theo thực tế hiệu quả như nó nhận được.

+0

Đây là cơ bản những gì tôi đã kết thúc. Jalf, và những người khác, đã thuyết phục tôi rằng tôi đã overdesigning toàn bộ điều. Một chức năng không cần phải làm cả hai phiên bản có thể thay đổi và không thay đổi được - và thực tế có thể gây nhầm lẫn hơn nếu một hàm hoạt động cả hai thứ đó. Tôi đoán tôi đã nhận được quá nhiều bởi ý tưởng sử dụng tài liệu tham khảo rvalue để làm một cái gì đó mát mẻ. Tôi ước gì tôi chưa bao giờ nghe nói đến các tài liệu tham khảo rvalue, sau đó tôi đã chọn giải pháp này ngay từ đầu và không lãng phí nửa ngày cố gắng làm điều gì đó lạ mắt! – karadoc

+0

Đừng lo lắng, những thứ tuyệt vời vẫn xảy ra - dưới mui xe;) – fredoverflow

1

Còn cách tiếp cận đơn giản sau đây là gì?

string& foo (string &change) // this accepts mutable string 
{ 
    change = string("hello ") + change; 
    return change; 
} 

string foo (const string &unchange) // this accepts not mutable string 
{ 
    return string("hello ") + unchange; 
} 

Xem số output here.

+0

Điều xấu về cách tiếp cận đó là phần thân của hàm phải được viết hai lần. Trong ví dụ cụ thể này nó tốt hơn những gì tôi có, nhưng nếu foo là một cái gì đó dài và phức tạp, thì điều này sẽ tăng gấp đôi mã yêu cầu. – karadoc

+2

@karadoc: bạn có thể dễ dàng viết cái không biến đổi về mặt biến đổi. 'int notModifying (const int & i) {int ii = i; trở lại sửa đổi (ii); } ' –

0

Trong bối cảnh đó là câu trả lời @ iammilind, nhưng sans trùng lặp:

#include <iostream> 
using namespace std; 

string foo(const string &unchange) { 
    return string("hello ") + unchange; 
} 

string& foo(string &change) { 
    return change = foo(static_cast<const string&>(foo)); 
} 

int main(int argc, char** argv) { 
    string a = "world"; 
    const string b = "immutable world"; 
    cout << foo(a) << '\n' << foo(b) << '\n'; 
    cout << foo(a) << '\n' << foo(b) << '\n'; 
} 

NB: Bạn cũng có thể sử dụng const_cast đây để thêm trình độ const.

2

Toàn bộ câu hỏi là tại sao bạn muốn có quá tải như vậy? Tất cả các quá tải này chỉ định một giao diện: foo (x). Nhưng x parameter có thể là thông số input hoặc input/output tùy thuộc vào loại của nó. Nó rất, rất dễ bị lỗi. Người dùng phải thực hiện thêm một số công việc để đảm bảo rằng biến của nó sẽ không bị đột biến. Không bao giờ làm điều đó trong mã sản xuất.

tôi sẽ đồng ý với quá tải như:

string foo(string &&in); 
string foo(const string& in); 

tham số đầu vào là không bao giờ thay đổi nếu nó không phải là một tạm thời và, đồng thời, bạn sử dụng lại đối tượng tạm thời. Nó có vẻ khá hợp lý.

Nhưng, tại sao bạn muốn tạo nhiều quá tải như vậy? & & quá tải là để tối ưu hóa. Tôi sẽ nói tối ưu hóa rất tinh tế. Bạn có chắc là bạn cần nó ở nhiều nơi không?

Dù sao, nếu bạn thực sự muốn tạo mã C++, mẫu không phải là lựa chọn thực sự tốt. Tôi sẽ sử dụng một số công cụ bên ngoài cho nó. Cá nhân tôi thích Cog.

0

Nếu bạn không lo lắng về hiệu quả, bạn có thể chuyển giá trị hoặc vượt qua tham chiếu const và thực hiện sao chép và thực hiện với nó.

Tuy nhiên, nếu bạn lo lắng về hiệu quả, tôi không nghĩ rằng đề xuất vượt qua theo giá trị trong số reply là cách tiếp cận tốt nhất. Điều này là bởi vì tôi nghĩ rằng nó kết quả trong bản sao/di chuyển thêm, như NRVO chỉ có vẻ làm việc với các biến địa phương, không phải tham số. Tôi nghĩ cách mà tránh moves/bản trong C++ 0x là quá tải kép, được minh họa bằng đoạn mã sau:

#include <iostream> 

struct A 
{ 
    A() : i(0) {} 
    A(const A& x) : i(x.i) { std::cout << "Copy" << std::endl; } 
    A(A&& x) : i(x.i) { std::cout << "Move" << std::endl; } 
    void inc() { ++i; } 
    int i; 
}; 

A f1(const A& x2) { A x = x2; x.inc(); return x; } 
A&& f1(A&& x) { x.inc(); return std::move(x); } 

A f2(A x) { x.inc(); return std::move(x); } 

int main() 
{ 
    A x; 
    std::cout << "A a1 = f1(x);" << std::endl; 
    A a1 = f1(x); 
    std::cout << "A a2 = f1(A());" << std::endl; 
    A a2 = f1(A()); 
    std::cout << "A b1 = f2(x);" << std::endl; 
    A b1 = f2(x); 
    std::cout << "A b2 = f2(A());" << std::endl; 
    A b2 = f2(A()); 
    std::cout << std::endl; 
    std::cout << "A a3 = f1(f1(x));" << std::endl; 
    A a3 = f1(f1(x)); 
    std::cout << "A a4 = f1(f1(A()));" << std::endl; 
    A a4 = f1(f1(A())); 
    std::cout << "A b3 = f2(f2(x));" << std::endl; 
    A b3 = f2(f2(x)); 
    std::cout << "A b4 = f2(f2(A()));" << std::endl; 
    A b4 = f2(f2(A())); 
    std::cout << std::endl; 
    std::cout << "A a5 = f1(f1(f1(x)));" << std::endl; 
    A a5 = f1(f1(f1(x))); 
    std::cout << "A a6 = f1(f1(f1(A())));" << std::endl; 
    A a6 = f1(f1(f1(A()))); 
    std::cout << "A b5 = f2(f2(f2(x)));" << std::endl; 
    A b5 = f2(f2(f2(x))); 
    std::cout << "A b6 = f2(f2(f2(A())));" << std::endl; 
    A b6 = f2(f2(f2(A()))); 
} 

nào tạo ra kết quả như sau:

A a1 = f1(x); 
Copy 
A a2 = f1(A()); 
Move 
A b1 = f2(x); 
Copy 
Move 
A b2 = f2(A()); 
Move 

A a3 = f1(f1(x)); 
Copy 
Move 
A a4 = f1(f1(A())); 
Move 
A b3 = f2(f2(x)); 
Copy 
Move 
Move 
A b4 = f2(f2(A())); 
Move 
Move 

A a5 = f1(f1(f1(x))); 
Copy 
Move 
A a6 = f1(f1(f1(A()))); 
Move 
A b5 = f2(f2(f2(x))); 
Copy 
Move 
Move 
Move 
A b6 = f2(f2(f2(A()))); 
Move 
Move 
Move 

Bạn có thể có thể để làm một số mẫu thủ thuật để tránh phải viết nhiều quá tải, ví dụ:

template <class T> 
param_return_type<T&&>::type f3(T&& y, typename std::enable_if<...>::type* dummy = 0) 
{ 
    typedef return_t param_return_type<T&&>::type; 
    return_t x = static_cast<return_t>(y); 
    x.inc(); 
    return static_cast<return_t>(x); 
} 

đâu param_return_type<T>::typeT khi qua (const) T&, và T&& khi vượt qua T&&. std::enable_if<...> bạn có thể sử dụng nếu bạn chỉ muốn mẫu này có các tham số cụ thể.

Tôi không biết cách viết định nghĩa param_return_type<T>::type, vì dường như không có std::remove_lvalue_reference. Nếu có ai biết cách, hãy chỉnh sửa/thêm vào bài viết của tôi.