Làm thế nào để elide sao chép khi xích?


10

Tôi đang tạo một lớp kiểu chuỗi, chẳng hạn như ví dụ nhỏ dưới đây. Có vẻ như khi xâu chuỗi các hàm thành viên, thì hàm tạo sao chép được gọi. Có cách nào để thoát khỏi cuộc gọi xây dựng bản sao? Trong ví dụ về đồ chơi của tôi dưới đây, rõ ràng là tôi chỉ xử lý tạm thời và do đó "nên" (có thể không theo tiêu chuẩn, nhưng về mặt logic) là một cuộc bầu chọn. Sự lựa chọn tốt thứ hai, để sao chép elision, sẽ là cho hàm tạo di chuyển được gọi, nhưng đây không phải là trường hợp.

class test_class {
    private:
    int i = 5;
    public:
    test_class(int i) : i(i) {}
    test_class(const test_class& t) {
        i = t.i;
        std::cout << "Copy constructor"<< std::endl;
    }
    test_class(test_class&& t) {
        i = t.i;
        std::cout << "Move constructor"<< std::endl;
    }
    auto& increment(){
        i++;
        return *this;
    }
};
int main()
{
    //test_class a{7};
    //does not call copy constructor
    auto b = test_class{7};
    //calls copy constructor
    auto b2 = test_class{7}.increment();
    return 0;
}

Chỉnh sửa: Một số làm rõ. 1. Điều này không phụ thuộc vào mức độ tối ưu hóa. 2. Trong mã thực của tôi, tôi có các đối tượng phức tạp (ví dụ như heap được phân bổ) so với ints


Mức độ tối ưu hóa nào bạn sử dụng để biên dịch?
JVApen

2
auto b = test_class{7};không gọi hàm tạo sao chép vì nó thực sự tương đương test_class b{7};và trình biên dịch đủ thông minh để nhận ra trường hợp này và do đó có thể dễ dàng bỏ qua mọi bản sao. Điều tương tự không thể được thực hiện cho b2.
Một số lập trình viên anh chàng

Trong ví dụ hiển thị, thực tế có thể không có bất kỳ hoặc nhiều sự khác biệt giữa di chuyển và sao chép và không phải ai cũng nhận thức được điều này. Nếu bạn mắc kẹt một cái gì đó như một vectơ lớn trong đó thì đó có thể là một vấn đề khác. Thông thường di chuyển chỉ có ý nghĩa đối với tài nguyên bằng cách sử dụng các loại (như sử dụng nhiều mem heap, v.v.) - đó có phải là trường hợp ở đây không?
darune

Ví dụ có vẻ giả tạo. Bạn có thực sự có I / O ( std::cout) trong ctor sao chép của mình không? Không có nó, bản sao nên được tối ưu hóa đi.
rustyx

@rustyx, xóa std :: cout và làm cho hàm tạo sao chép rõ ràng. Điều này chứng tỏ rằng cuộc bầu chọn sao chép không phụ thuộc vào std :: cout.
DDaniel

Câu trả lời:


7
  1. Câu trả lời một phần (nó không xây dựng b2tại chỗ, nhưng biến cấu trúc sao chép thành cấu trúc di chuyển): Bạn có thể quá tải incrementhàm thành viên trên danh mục giá trị của thể hiện liên quan:

    auto& increment() & {
        i++;
        return *this;
    }
    
    auto&& increment() && {
        i++;
       return std::move(*this);
    }

    Điều này gây ra

    auto b2 = test_class{7}.increment();

    để di chuyển-xây dựng b2test_class{7}là tạm thời và &&quá tải test_class::incrementđược gọi.

  2. Đối với một công trình tại chỗ thực sự (nghĩa là thậm chí không phải là một công trình di chuyển), bạn có thể biến tất cả các chức năng thành viên đặc biệt và không đặc biệt thành các constexprphiên bản. Sau đó, bạn có thể làm

    constexpr auto b2 = test_class{7}.increment();

    và bạn không phải là một di chuyển cũng không phải là một bản sao để trả tiền. Điều này rõ ràng là có thể cho đơn giản test_class, nhưng không phải cho một kịch bản tổng quát hơn mà không cho phép các constexprchức năng thành viên.


Sự thay thế thứ hai cũng làm cho b2không thể sửa đổi.
Một số lập trình viên anh chàng

1
@Someprogrammerdude Điểm tốt. Tôi đoán constinitsẽ là cách để đi đến đó.
Lubgr

Mục đích của ký hiệu sau tên hàm là gì? Tôi chưa bao giờ thấy điều đó trước đây.
Philip Nelson

1
@PhilipNelson họ là những người tham gia vòng loại và có thể ra lệnh thisgiống như constchức năng của thành viên
kmdreko

1

Về cơ bản, việc gán một cho một đòi hỏi phải gọi một hàm tạo, tức là một bản sao hoặc di chuyển . Điều này khác với trong đó cả hai mặt của hàm là cùng một đối tượng riêng biệt. Ngoài ra một có thể đề cập đến một đối tượng được chia sẻ giống như một con trỏ.


Cách đơn giản nhất có lẽ là làm cho hàm tạo sao chép được tối ưu hóa hoàn toàn. Cài đặt giá trị đã được tối ưu hóa bởi trình biên dịch, nó chỉ std::coutlà không thể được tối ưu hóa.

test_class(const test_class& t) = default;

(hoặc chỉ loại bỏ cả bản sao và di chuyển hàm tạo)

ví dụ sống


Vì vấn đề của bạn là cơ bản với tham chiếu, nên một giải pháp có thể không trả về tham chiếu cho đối tượng nếu bạn muốn dừng sao chép theo cách này.

  void increment();
};

auto b = test_class{7};//does not call copy constructor
b.increment();//does not call copy constructor

Phương pháp thứ ba chỉ dựa vào việc sao chép bản sao ở vị trí đầu tiên - tuy nhiên điều này đòi hỏi phải viết lại hoặc đóng gói hoạt động vào một chức năng và do đó tránh được vấn đề hoàn toàn (tôi biết đây có thể không phải là điều bạn muốn, nhưng có thể là giải pháp cho người dùng khác):

auto b2 = []{test_class tmp{7}; tmp.increment().increment().increment(); return tmp;}(); //<-- b2 becomes 10 - copy constructor not called

Phương thức thứ tư là sử dụng di chuyển thay vào đó, hoặc được gọi rõ ràng

auto b2 = std::move(test_class{7}.increment());

hoặc như đã thấy trong câu trả lời này .


@ O'Neil đã nghĩ đến một trường hợp khác - ty, đã sửa
darune
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.