Con trỏ duy nhất - Tại sao hàm hủy được gọi là 3 lần


8

Tôi có một phương thức trả về một đối tượng theo giá trị. Phương pháp này xuất phát từ một thư viện mà tôi không kiểm soát được. Để xử lý thêm đối tượng, tôi muốn tiếp tục làm việc với unique_ptr trên đối tượng này. Đây là một ví dụ:

#include <iostream>
#include <memory>

class Bla {
  public:
      Bla() { std::cout << "Constructor!\n"; }
      ~Bla() { std::cout << "Destructor!\n"; }
};

Bla GetBla() {
  Bla bla;
  return std::move(bla);
}

int main() {
  auto bla = std::make_unique<Bla>(GetBla());
}

Ví dụ tạo ra đầu ra sau:

Constructor!
Destructor!
Destructor!
Destructor!

Tại sao kẻ hủy diệt của Bla được gọi 3 lần ở đây? Là cách tôi tạo unique_prt có đúng không?


4
Tôi đề nghị bạn sử dụng trình gỡ lỗi để đặt điểm dừng trong hàm hủy. Sau đó, bạn có thể dễ dàng nhìn thấy từ nơi các hàm hủy được gọi.
Một số lập trình viên anh chàng

3
Gợi ý: std::movekhông di chuyển bất cứ điều gì. Nó chỉ đúc từ loại này sang loại khác.
abhiarora

4
Xác định bản sao và di chuyển các nhà xây dựng để xem hình ảnh đầy đủ. Ngoài ra sử dụng std::movetrên returnlà một sai lầm lớn.
Marek R

6
Đừng trả lại một đối tượng bằng std :: move. Điều này ngăn RVO.
Daniel Schlößer

Câu trả lời:


11

Thực sự có 3 lần một ví dụ Blađược xây dựng.

Bla GetBla() {
  Bla bla;    // 1st construction
  return std::move(bla); // 2nd construction (return by copy)
}

Đừng quay lại bằng cách di chuyển. Chỉ cần trả lại bla, trong hầu hết các trường hợp, bản sao sẽ bị xóa bỏ.

  auto bla = std::make_unique<Bla>(GetBla());  // 3rd construction - Bla copy construction

Lưu ý rằng make_unique<Bla>luôn luôn xây dựng một trường hợp mới. Trong trường hợp này bởi vì bạn đang vượt qua một thể hiện khác, nó trở thành bản sao xây dựng.

Một gợi ý rằng việc xây dựng sao chép diễn ra là hàm tạo mặc định của bạn chỉ được gọi một lần, trong khi hàm hủy được gọi 3 lần. Đó là bởi vì trong 2 trường hợp khác, hàm tạo sao chép (hoặc di chuyển) ẩn được gọi ( Bla::Bla(Bla const&)).


Cảm ơn đã giải thích, tôi nghĩ rằng std :: move được cho là để ngăn chặn việc sao chép, nhưng rõ ràng nó làm điều ngược lại.
Tobi S.

1
@TobiS. Nó sẽ là một cấu trúc di chuyển thay vì một cấu trúc sao chép, nhưng đối tượng của bạn không tự động có một hàm tạo di chuyển, bởi vì bạn đã xác định một hàm hủy .
dùng253751

@ user253751: Cảm ơn đã làm rõ.
Tobi S.

Bạn có nghĩ // 2nd construction (return by copy)là không đúng? Giá trị trả về sẽ được di chuyển xây dựng? Xem điều này (tôi có thể sai): stackoverflow.com/a/60487169/5735010 .
abhiarora

1
@abhiarora xem bình luận thứ 2. Không có nhà xây dựng di chuyển.
rustyx

3

Trình biên dịch thậm chí có thể cảnh báo bạn rằng

di chuyển một đối tượng cục bộ trong một tuyên bố trở lại ngăn chặn cuộc bầu cử sao chép.

Tôi không chắc chắn 100%, nhưng tôi nghĩ rằng bạn nhận được ba cuộc gọi giải mã từ:

  • Biến cục bộ blatừGetBla()
  • Giá trị trả về từ GetBla()sau khi được sử dụng trongstd::make_unique<Bla>(GetBla());
  • Rõ ràng là từ kẻ hủy diệt của std::unique_ptr

Cách dễ nhất là để std::make_uniqegọi hàm tạo mặc định của Bla:

auto bla = std::make_unique<Bla>(); // Calls Bla::Bla() to initalize the owned object
#include <iostream>
#include <memory>

class Bla {
  public:
      Bla() { std::cout << "Constructor!\n"; }
      ~Bla() { std::cout << "Destructor!\n"; }
};

int main() {
  auto bla = std::make_unique<Bla>();
}

Đầu ra

Constructor!
Destructor!

2

Cách đúng để tạo unique_ptr:

auto bla = std::make_unique<Bla>();

Tuy nhiên, mã của bạn tạo ra 3 trường hợp Bla:

  1. Đối tượng địa phương blatrong GetBla()chức năng.
  2. Giá trị trả về của GetBla().
  3. Cuối cùng, make_unique()tạo thêm một ví dụ.

GHI CHÚ:

  1. Với sự hiện diện của hàm hủy do người dùng định nghĩa, trình biên dịch không tạo ra hàm tạo di chuyển, vì vậy GetBla()giá trị trả về là một bản sao của đối tượng cục bộ bla.
  2. Kể từ khi GetBla()trả về moveđối tượng cục bộ, ed -elision bị loại bỏ.

1

Để thực sự thấy những gì xảy ra đằng sau hậu trường, bạn có thể sử dụng trình gỡ lỗi hoặc xác định hàm tạo sao chép . Tôi đã thêm các hàm tạo sao chép trong mã của bạn. Hãy thử mã được đưa ra dưới đây:

#include <iostream>
#include <memory>

class Bla {
public:
    Bla(void) 
    {
        std::cout << "Constructor!" << std::endl;
    }
    //Bla(Bla &&)
    //{
    //    std::cout << "Move Constructors" << std::endl;
    //}
    Bla(const Bla &)
    {
        std::cout << "Copy Constructors" << std::endl;
    }
    ~Bla(void)
    {
        std::cout << "Destructor!" << std::endl;
    }
private:
    int a = 2;
};

Bla GetBla(void) 
{
    Bla bla; // Default Constructor Called here
    return std::move(bla); // Second Construction over here
}

int main(void)
{
    auto bla = std::make_unique<Bla>(GetBla()); // Third Construction
    return 0;
} 

GHI CHÚ:

std::movekhông di chuyển bất cứ điều gì. Nó chỉ chuyển từ lvaluetham chiếu sang rvaluetham chiếu và đối tượng trả về của bạn có thể đã được xây dựng thông qua một hàm tạo di chuyển (và có thể bị loại bỏ bản sao ) nhưng trình biên dịch không khai báo hàm movetạo bởi vì bạn đã xác định hàm hủy (& tôi đã thêm hàm tạo sao chép trong ví dụ của tôi)

Đầu ra:

Constructor! # 1
Copy Constructors # 2
Destructor! # 3
Copy Constructors # 4
Destructor! # 5
Destructor! # 6

Xem những bình luận của tôi bên dưới:

  1. Đối tượng blađược xây dựng trong chức năng GetBla()thông qua hàm tạo mặc định.
  2. Giá trị trả về của GetBla()hàm được sao chép từ đối tượng được tạo trong # 1.
  3. bla đối tượng (được xây dựng trong # 1) bị phá hủy và hàm hủy của nó được gọi.
  4. std::make_unique<Bla>gọi newvà sau đó gọi hàm tạo thích hợp và nó chọn hàm copytạo.
  5. Đối tượng được tạo trong # 2 bị phá hủy.
  6. Cuối cùng, đối tượng được tạo ở # 4 bị phá hủy.
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.