Làm thế nào để giải quyết


18

Chương trình ngắn sau

#include <vector>
#include <iostream>

std::vector<int> someNums()
{
    return {3, 5, 7, 11};
}

class Woop
{
public:
    Woop(const std::vector<int>& nums) : numbers(nums) {}
    void report()
    {
        for (int i : numbers)
            std::cout << i << ' ';
        std::cout << '\n';
    }
private:
    const std::vector<int>& numbers;
};

int main()
{
    Woop woop(someNums());
    woop.report();
}

có một vấn đề tham chiếu lơ lửng, mà dường như không có trình biên dịch nào cảnh báo. Vấn đề là thời gian có thể bị ràng buộc với const-refs, sau đó bạn có thể giữ xung quanh. Câu hỏi sau đó là; Có một phương pháp để tránh đi vào vấn đề này? Tốt nhất là một thứ không liên quan đến việc hy sinh tính chính xác của const, hoặc luôn tạo ra các bản sao của các vật thể lớn.


4
Đó là khó khăn. Tôi có thể chắc chắn với bạn rằng tôi nghĩ hai lần trước khi tôi tạo một tham chiếu const thành viên. Nếu nghi ngờ, tôi sẽ xem xét mô hình hóa dữ liệu này bằng cách nào đó có thể tham gia vào con trỏ thông minh ( std::unique_ptrvì quyền sở hữu độc quyền, hoặc std::shared_ptrquyền sở hữu chung, hoặc std::weak_ptr, ít nhất, nhận ra dữ liệu bị mất).
Scheff

Trong C ++, bạn không trả tiền cho những gì bạn không cần / sử dụng. Lập trình viên phải quan tâm rằng thời gian tồn tại của đối tượng được giới thiệu không kết thúc trong khi tham chiếu vẫn đang được sử dụng / hiện có. Điều tương tự đối với con trỏ thô, ... Có con trỏ thông minh để mang đến cho bạn các tính năng bạn yêu cầu :)
Fareanor

2
Các thành viên tham khảo luôn là một sai lầm: Herbutter.com/2020/02/23/references-simply
Maxim Egorushkin

Mặc dù trình biên dịch không cảnh báo, lỗi này có thể bắt được bởi Valgrind và -fsanitize=address. Tôi không nghĩ rằng có bất kỳ thực hành tốt nhất để tránh nó mà không làm giảm hiệu suất.
ks1322

Câu trả lời:


8

Trong trường hợp khi một số phương thức giữ một tham chiếu sau khi trả về, đó là một ý tưởng tốt để sử dụng std::reference_wrapperthay vì tham chiếu thông thường:

#include <functional>

class Woop
{
public:
    using NumsRef = ::std::reference_wrapper<const std::vector<int>>;
    Woop(NumsRef nums) : numbers_ref{nums} {}
    void report()
    {
        for (int i : numbers_ref.get())
            std::cout << i << ' ';
        std::cout << '\n';
    }
private:
    NumsRef numbers_ref;
};
  1. nó đã đi kèm với một tập hợp các tình trạng quá tải ngăn chặn sự ràng buộc của các giá trị và việc vượt thời gian ngoài ý muốn, do đó không cần phải bận tâm đến việc quá tải bị cấm thêm khi sử dụng một giá Woop (std::vector<int> const &&) = delete;trị cho phương pháp của bạn:
Woop woop{someNums()}; // error
woop.report();
  1. nó cho phép ràng buộc ngầm định các giá trị để nó không phá vỡ các yêu cầu hợp lệ hiện có:
auto nums{someNums()};
Woop woop{nums}; // ok
woop.report();
  1. nó cho phép ràng buộc rõ ràng các giá trị vốn là một thông lệ tốt để chỉ ra rằng người gọi sẽ giữ tham chiếu sau khi trả về:
auto nums{someNums()};
Woop woop{::std::ref(nums)}; // even better because explicit
woop.report();

10

Một cách để làm cho lớp của bạn ít bị tổn thương hơn có thể là thêm một hàm tạo đã bị xóa mà có quyền ref. Điều này sẽ ngăn cá thể lớp của bạn thực hiện các ràng buộc với tạm thời.

Woop(std::vector<int>&& nums)  =delete;

Hàm tạo bị xóa này thực sự sẽ làm cho mã O / P không được biên dịch, đó có thể là hành vi bạn đang tìm kiếm?


3

Tôi đồng ý với các câu trả lời và nhận xét khác rằng bạn nên suy nghĩ cẩn thận nếu bạn thực sự cần lưu trữ một tài liệu tham khảo trong lớp. Và nếu bạn làm như vậy, có lẽ bạn sẽ muốn một con trỏ không phải là const vector thay thế (tức là std::vector<int> const * numbers_).

Tuy nhiên, nếu đó là trường hợp, tôi thấy rằng các câu trả lời khác hiện đang được đăng bên cạnh quan điểm. Tất cả đều chỉ cho bạn cách tạo ra Woopnhững giá trị đó.

Nếu bạn có thể đảm bảo rằng vectơ bạn truyền vào sẽ tồn tại lâu hơn Woopcá thể của bạn , thì bạn có thể vô hiệu hóa rõ ràng việc xây dựng một Wooptừ một giá trị. Điều đó có thể sử dụng cú pháp C ++ 11 này:

Woop (std::vector<int> const &&) = delete;

Bây giờ mã ví dụ của bạn sẽ không biên dịch nữa. Trình biên dịch có lỗi tương tự như:

prog.cc: In function 'int main()':
prog.cc:29:25: error: use of deleted function 'Woop::Woop(const std::vector<int>&&)'
   29 |     Woop woop(someNums());
      |                         ^
prog.cc:15:5: note: declared here
   15 |     Woop(std::vector<int> const &&) = delete;
      |     ^~~~

PS: Bạn có thể muốn một nhà xây dựng rõ ràng, xem ví dụ: Từ khóa rõ ràng có nghĩa là gì? .


Tôi dường như đã đánh cắp câu trả lời của bạn ở đó. Lấy làm tiếc!
Gem Taylor

1

Để ngăn chặn trường hợp cụ thể đó, bạn có thể chọn lấy một con trỏ (vì Weep(&std::vector<int>{1,2,3})không được phép) hoặc bạn có thể lấy tham chiếu không phải là lỗi cũng sẽ tạm thời bị lỗi.

Woop(const std::vector<int> *nums);
Woop(std::vector<int> *nums);
Woop(std::vector<int>& nums);

Những điều này vẫn không đảm bảo giá trị vẫn còn hiệu lực, nhưng ít nhất là dừng sai lầm dễ dàng nhất, không tạo ra một bản sao và không cần phải numsđược tạo theo một cách đặc biệt (ví dụ như std::shared_ptrhoặc std::weak_ptrkhông).

std::scoped_locktham chiếu đến mutex sẽ là một ví dụ và một trong đó ptr duy nhất / chia sẻ / yếu không thực sự muốn. Thường std::mutexsẽ chỉ là một thành viên cơ bản hoặc biến cục bộ. Bạn vẫn phải rất cẩn thận, nhưng trong những trường hợp này thường dễ xác định tuổi thọ.

std::weak_ptrlà một tùy chọn khác cho việc không sở hữu, nhưng sau đó bạn buộc người gọi sử dụng shared_ptr(và do đó cũng phân bổ heap), và đôi khi điều đó không muốn.

Nếu một bản sao là OK, điều đó chỉ tránh được vấn đề.

Nếu Woopnên có quyền sở hữu hoặc vượt qua dưới dạng giá trị r và di chuyển (và tránh hoàn toàn các vấn đề về con trỏ / tham chiếu) hoặc sử dụng unique_ptrnếu bạn không thể tự di chuyển giá trị hoặc muốn con trỏ vẫn hợp lệ.

// the caller can't continue to use nums, they could however get `numbers` from Woop or such like
// or just let Woop only manipulate numbers directly.
Woop(std::vector<int> &&nums) 
   : numbers(std::move(nums)) {}
std::vector<int> numbers;

// while the caller looses the unique_ptr, they might still use a raw pointer, but be careful.
// Or again access numbers only via Woop as with the move construct above.
Woop(std::unique_ptr<std::vector<int>> &&nums) 
    : numbers(std::move(nums)) {}
std::unique_ptr<std::vector<int>> numbers;

Hoặc nếu quyền sở hữu được chia sẻ, bạn có thể sử dụng shared_ptrcho tất cả mọi thứ và nó sẽ bị xóa cùng với tham chiếu cuối cùng, nhưng điều này có thể khiến việc theo dõi vòng đời của đối tượng trở nên rất khó hiểu nếu sử dụng quá mức.


1

Bạn có thể sử dụng template programmingarraysnếu bạn muốn có một đối tượng chứa một constcontainer. Do các nhà constexprxây dựng và constexpr arraysbạn đạt được const correctnesscompile time execution.

Đây là một bài viết có thể thú vị: std :: di chuyển một vectơ const

#include <array>
#include <iostream>
#include <vector>


std::array<int,4>  someNums()
{
    return {3, 5, 7, 11};
}


template<typename U, std::size_t size>
class Woop
{
public:

template<typename ...T>
    constexpr Woop(T&&... nums) : numbers{nums...} {};

    template<typename T, std::size_t arr_size>
    constexpr Woop(std::array<T, arr_size>&& arr_nums) : numbers(arr_nums) {};

    void report()
    const {
        for (auto&& i : numbers)
            std::cout << i << ' ';
         std::cout << '\n';
    }



private: 
    const std::array<U, size> numbers;
    //constexpr vector with C++20
};

int main()
{
    Woop<int, 4> wooping1(someNums());
    Woop<int, 7> wooping2{1, 2, 3, 5, 12 ,3 ,51};

    wooping1.report();
    wooping2.report();
    return 0;
}

mã vận hành

Đầu ra:

3 5 7 11                                                                                                                        
1 2 3 5 12 3 51

1
Với những con số như std::arraythế này được đảm bảo sao chép, ngay cả khi di chuyển sẽ có sẵn. Trên hết wooping1wooping2không cùng loại, ít hơn lý tưởng.
sp2danny

@ sp2danny cảm ơn bạn đã phản hồi và tôi phải đồng ý với bạn về cả hai điểm. user7860670 cung cấp một giải pháp tốt hơn :)
M.Mac
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.