Di chuyển ngữ nghĩa trong C ++ - Di chuyển trở lại của các biến cục bộ


10

Tôi hiểu rằng trong C ++ 11, khi bạn trả về một biến cục bộ từ một hàm theo giá trị, trình biên dịch được phép coi biến đó là một tham chiếu giá trị r và 'di chuyển' nó ra khỏi hàm để trả về nó (nếu RVO / NRVO không xảy ra thay vào đó, tất nhiên).

Câu hỏi của tôi là, điều này không thể phá vỡ mã hiện có?

Hãy xem xét các mã sau đây:

#include <iostream>
#include <string>

struct bar
{
  bar(const std::string& str) : _str(str) {}
  bar(const bar&) = delete;
  bar(bar&& other) : _str(std::move(other._str)) {other._str = "Stolen";}
  void print() {std::cout << _str << std::endl;}

  std::string _str;
};

struct foo
{
  foo(bar& b) : _b(b) {}
  ~foo() {_b.print();}

  bar& _b;
};

bar foobar()
{
  bar b("Hello, World!");
  foo f(b);

  return std::move(b);
}

int main()
{
  foobar();
  return EXIT_SUCCESS;
}

Tôi nghĩ rằng một kẻ hủy diệt của một đối tượng cục bộ có thể tham chiếu đến đối tượng được di chuyển ngầm, và do đó bất ngờ nhìn thấy một đối tượng 'trống rỗng'. Tôi cố gắng để kiểm tra điều này (xem http://ideone.com/ZURoeT ), nhưng tôi đã có kết quả chính xác 'mà không có sự rõ ràng std::movetrong foobar(). Tôi đoán đó là do NRVO, nhưng tôi đã không cố gắng sắp xếp lại mã để vô hiệu hóa điều đó.

Tôi có đúng không khi chuyển đổi này (gây ra sự dịch chuyển khỏi chức năng) xảy ra ngầm và có thể phá vỡ mã hiện có?

CẬP NHẬT Dưới đây là một ví dụ minh họa những gì tôi đang nói. Hai liên kết sau đây cho cùng một mã. http://ideone.com/4GFIRu - C ++ 03 http://ideone.com/FcL2Xj - C ++ 11

Nếu bạn nhìn vào đầu ra, nó khác.

Vì vậy, tôi đoán rằng câu hỏi này bây giờ đã trở thành, điều này có được xem xét khi thêm di chuyển ngầm vào tiêu chuẩn hay không, và đã quyết định rằng có thể thêm thay đổi vi phạm này không vì loại mã này có đủ hiếm không? Tôi cũng tự hỏi liệu có trình biên dịch nào sẽ cảnh báo trong những trường hợp như thế này không ...


Một động thái phải luôn luôn để đối tượng ở trạng thái hủy diệt.
Zan Lynx

Vâng, nhưng đó không phải là câu hỏi. Mã Pre-c ++ 11 có thể giả định rằng giá trị của biến cục bộ sẽ không thay đổi chỉ đơn giản là do trả về nó, vì vậy động thái ngầm này có thể phá vỡ giả định đó.
Bwmat

Đó là những gì tôi đã cố gắng làm sáng tỏ trong ví dụ của mình; thông qua các hàm hủy, bạn có thể kiểm tra trạng thái (một tập hợp con) của một biến cục bộ của hàm 'sau' câu lệnh return thực thi, nhưng trước khi hàm thực sự trả về.
Bwmat

Đây là một câu hỏi xuất sắc với ví dụ bạn đã thêm. Tôi hy vọng điều này nhận được nhiều câu trả lời từ các chuyên gia có thể làm sáng tỏ điều này. Phản hồi thực sự duy nhất tôi có thể đưa ra là: đây là lý do tại sao các đối tượng thường không nên có quan điểm không sở hữu vào dữ liệu. Thực tế có nhiều cách để viết mã tìm kiếm vô tội mà phân biệt khi bạn đưa ra các đối tượng các khung nhìn không sở hữu (các con trỏ thô hoặc các tham chiếu). Tôi có thể giải thích điều này bằng một câu trả lời thích hợp nếu bạn thích, nhưng tôi đoán đó không phải là điều bạn muốn thực sự nghe về. Và btw, đã biết rằng 11 có thể phá vỡ mã hiện tại, ví dụ như bằng cách xác định các từ khóa mới.
Nir Friedman

Vâng, tôi biết rằng C ++ 11 không bao giờ tuyên bố không phá vỡ bất kỳ mã cũ nào, nhưng điều này khá tinh tế và sẽ rất dễ bị bỏ lỡ (không có lỗi biên dịch, cảnh báo, segfaults)
Bwmat

Câu trả lời:


8

Scott Meyers đã đăng lên comp.lang.c ++ (tháng 8 năm 2010) về một vấn đề trong đó việc tạo ra các hàm tạo di chuyển ngầm có thể phá vỡ các bất biến lớp C ++ 03:

struct X
{
  // invariant: v.size() == 5
  X() : v(5) {}

  ~X() { std::cout << v[0] << std::endl; }

private:    
  std::vector<int> v;
};

int main()
{
    std::vector<X> y;
    y.push_back(X()); // X() rvalue: copied in C++03, moved in C++0x
}

Vấn đề ở đây là trong C ++ 03, Xcó một bất biến là vthành viên của nó luôn có 5 phần tử. X::~X()được tính vào bất biến đó, nhưng hàm tạo di chuyển mới được giới thiệu đã di chuyển từ vđó, do đó đặt độ dài của nó thành 0.

Điều này có liên quan đến ví dụ của bạn vì bất biến bị hỏng chỉ được phát hiện trong hàm Xhủy của (như bạn nói rằng có thể cho hàm hủy của một đối tượng cục bộ tham chiếu đến đối tượng được di chuyển ngầm và do đó bất ngờ nhìn thấy một đối tượng trống rỗng ).

C ++ 11 cố gắng đạt được sự cân bằng giữa việc phá vỡ một số mã hiện có và cung cấp các tối ưu hóa hữu ích dựa trên các hàm tạo di chuyển.

Ủy ban ban đầu quyết định rằng các nhà xây dựng di chuyển và các toán tử chuyển nhượng di chuyển nên được tạo bởi trình biên dịch khi không được cung cấp bởi người dùng.

Sau đó, quyết định rằng đây thực sự là nguyên nhân gây ra báo động và nó đã hạn chế việc tạo ra các hàm tạo di chuyển tự động và các toán tử gán chuyển động theo cách mà ít có khả năng, mặc dù không thể, cho mã hiện tại bị phá vỡ (ví dụ như hàm hủy được xác định rõ ràng).

Thật hấp dẫn khi nghĩ rằng việc ngăn chặn việc tạo ra các hàm tạo di chuyển ngầm khi có hàm hủy do người dùng xác định là đủ nhưng điều đó không đúng ( N3153 - Di chuyển ngầm phải đi để biết thêm chi tiết).

Trong N3174 - Di chuyển hay không di chuyển Stroupstrup nói:

Tôi coi đây là một vấn đề thiết kế ngôn ngữ, thay vì một vấn đề tương thích ngược đơn giản. Thật dễ dàng để tránh phá vỡ mã cũ (ví dụ: chỉ xóa các hoạt động di chuyển khỏi C ++ 0x), nhưng tôi thấy làm cho C ++ 0x trở thành ngôn ngữ tốt hơn bằng cách làm cho các hoạt động di chuyển lan tỏa thành mục tiêu chính mà nó có thể đáng để phá vỡ một số C + Mã +98.

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.