2012-05-06 9 views
7

Hãy xem xét các lớp:C++ 11 thông số tối ưu qua

#include <iostream> 
#include <string> 

class A 
{ 
    std::string test; 
public: 
    A (std::string t) : test(std::move(t)) {} 
    A (const A & other) { *this = other; } 
    A (A && other) { *this = std::move(other); } 

    A & operator = (const A & other) 
    { 
     std::cerr<<"copying A"<<std::endl; 
     test = other.test; 
     return *this; 
    } 

    A & operator = (A && other) 
    { 
     std::cerr<<"move A"<<std::endl; 
     test = other.test; 
     return *this; 
    } 
}; 

class B 
{ 
    A a; 
public: 
    B (A && a) : a(std::move(a)) {} 
    B (A const & a) : a(a) {} 
}; 

Khi tạo một B, tôi luôn luôn có một con đường phía trước tối ưu cho A, một động thái cho rvalues ​​hoặc một bản sao cho lvalues.

Có thể đạt được kết quả tương tự với một hàm tạo không? Nó không phải là một vấn đề lớn trong trường hợp này, nhưng còn nhiều thông số thì sao? Tôi sẽ cần sự kết hợp của mọi sự xuất hiện có thể của các giá trị và giá trị trong danh sách tham số.

Điều này không giới hạn đối với các nhà xây dựng, mà còn áp dụng cho các tham số chức năng (ví dụ: người định cư).

Lưu ý: Câu hỏi này đúng về class B; class A chỉ tồn tại để hình dung cách thức thực hiện cuộc gọi sao chép/di chuyển.

+0

Bạn nên đọc: http://stackoverflow.com/questions/8472208/under-what-conditions-should-i-be-thinking-about-implementing-a-move-constructor và http: // stackoverflow. com/questions/4782757/rule-of-three-trở thành-rule-of-five-with-c11 –

+0

@JamesCuster: Tôi chỉ muốn kiểm tra số lần các nhà thầu/nhà khai thác tương ứng được gọi. – fscan

Trả lời

9

Cách tiếp cận "theo giá trị" là một tùy chọn. Nó không phải là tối ưu như những gì bạn có, nhưng chỉ đòi hỏi một quá tải:

class B 
{ 
    A a; 
public: 
    B (A _a) : a(move(_a)) {} 
}; 

Chi phí là 1 xây dựng di chuyển thêm cho cả lvalues ​​và xvalues, nhưng điều này vẫn còn tối ưu cho prvalues ​​(1 move). Một "xvalue" là một giá trị đã được đúc thành rvalue bằng cách sử dụng std :: move.

Bạn cũng có thể thử một "chuyển tiếp hoàn hảo" giải pháp:

class B 
{ 
    A a; 
public: 
    template <class T, 
       class = typename std::enable_if 
       < 
       std::is_constructible<A, T>::value 
       >::type> 
    B (T&& _a) : a(std::forward<T>(_a)) {} 
}; 

này sẽ giúp bạn trở lại với số lượng tối ưu của công trình sao chép/di chuyển. Nhưng bạn nên hạn chế hàm tạo mẫu sao cho nó không quá chung chung. Bạn có thể thích sử dụng is_convertible thay vì is_constructible như tôi đã làm ở trên. Đây cũng là một giải pháp xây dựng duy nhất, nhưng khi bạn thêm các tham số, ràng buộc của bạn ngày càng trở nên phức tạp.

Lưu ý: Lý do ràng buộc là cần thiết ở trên là vì không có, khách hàng của B sẽ nhận được câu trả lời sai khi họ truy vấn std::is_constructible<B, their_type>::value. Nó sẽ trả lời sai sự thật nếu không có ràng buộc thích hợp trên B.

Tôi sẽ nói rằng không có giải pháp nào trong số này luôn tốt hơn các giải pháp khác. Có sự cân bằng kỹ thuật được thực hiện ở đây.

+0

liên quan đến giá trị bằng: trong thử nghiệm của tôi, tôi đã có thêm một chuyển động cho các giá trị thực (vs2010). – fscan

+0

Thú vị. Cảm ơn thông tin đó. Tôi không có quyền truy cập vào vs2010. –

+0

Ok, điều này là tốt đẹp, nhưng loại làm cho mã không đọc được nếu áp dụng cho mọi constructor/setter. Có một trường hợp tôi sẽ không muốn hành vi này từ một nhà xây dựng/setter. Nếu không, trình biên dịch có thể làm điều này theo mặc định nếu tôi khai báo theo giá trị không? (cũng tự động chuyển thành xrvalue nếu lvalue không bao giờ được sử dụng nữa, như trong hàm khởi tạo này) – fscan

2

Sử dụng một loại tham số suy luận cho các nhà xây dựng cho B:

template <typename T> explicit B(T && x) : a(std::forward<T>(x) { } 

này sẽ làm việc cho bất kỳ tranh luận mà từ đó một đối tượng A là constructible.

Nếu A có nhiều hàm tạo với số đối số thay đổi, bạn chỉ có thể thực hiện toàn bộ điều variadic bằng cách thêm ... ở mọi nơi.

Vì @Howard cho biết, tuy nhiên, bạn nên thêm ràng buộc để lớp không xuất hiện được từ các đối số mà từ đó nó thực sự không có.

1

Nếu string trong mẫu của bạn là std::string, chỉ cần không quan tâm: bản sao được cung cấp mặc định và chuyển các cuộc gọi của họ trong các thành viên tương ứng.Và std::string đã sao chép và di chuyển cả hai được triển khai, do đó thời gian được di chuyển, các biến được sao chép.

Không cần phải xác định bản sao cụ thể và di chuyển ctor và gán. Bạn chỉ có thể để lại với các nhà xây dựng

A::A(string s) :test(std::move(s)) {} 

Nói chung việc thực hiện đơn giản của sao chép và di chuyển có thể là sau

class A 
{ 
public: 
    A() :p() {} 

    A(const A& a) :p(new data(*a.p)) {} //copy 
    A(A&& a) :p(a.p) { a.p=0; }   //move 

    A& operator=(A a) //note: pass by value 
    { clear(); swap(a); return *this; } 
    ~A() { clear(); } 

    void swap(A& a) { std::swap(p,a.p); } 
    void clear() { delete p; p=0; } 

private: 

    data* p; 
}; 

Các operator= mất một giá trị được nội di chuyển. Nếu nó đến từ một tạm thời được di chuyển, nếu nó đến từ một biến được sao chép. Sự khác biệt giữa sao chép và di chuyển đòi hỏi nhà xây dựng riêng biệt nhưng, nếu chúng ta lấy được A như

class B: public A 
{ 
... 
}; 

không cần phải ghi đè lên bất cứ điều gì, vì mặc định sao chép ctor cho B gọi là bản sao cho A, và di chuyển mặc định cho B gọi di chuyển cho A và tất cả các toán tử gán mặc định cho B gọi cho toán tử duy nhất được xác định cho A (di chuyển hoặc sao chép tùy thuộc vào những gì đã được chuyển tiếp).