Lý do để vượt qua một con trỏ bằng tham chiếu trong C ++?


97

Bạn muốn sử dụng mã có tính chất này trong trường hợp nào trong c ++?

void foo(type *&in) {...}

void fii() {
  type *choochoo;
  ...
  foo(choochoo);
}

nếu bạn cần phải trả lại một con trỏ - sử dụng tốt hơn một giá trị trả về
littleadv

6
Bạn có thể giải thích tại sao không? Lời khen này không hữu ích lắm. Câu hỏi của tôi là khá hợp pháp. Điều này hiện đang được sử dụng trong mã sản xuất. Tôi chỉ không hiểu hoàn toàn tại sao.
Matthew Hoggan

1
David tóm tắt nó khá độc đáo. Chính con trỏ đang được sửa đổi.
chris

2
Tôi vượt qua cách này nếu tôi sẽ gọi toán tử "Mới" trong hàm.
NDEthos

Câu trả lời:


142

Bạn sẽ muốn chuyển một con trỏ bằng tham chiếu nếu bạn cần sửa đổi con trỏ chứ không phải đối tượng mà con trỏ đang trỏ tới.

Điều này tương tự như lý do tại sao con trỏ kép được sử dụng; sử dụng tham chiếu đến con trỏ an toàn hơn một chút so với sử dụng con trỏ.


14
Vì vậy, tương tự như một con trỏ của một con trỏ, ngoại trừ một mức độ điều hướng ít hơn cho ngữ nghĩa chuyển qua tham chiếu?

Tôi muốn nói thêm rằng đôi khi bạn cũng muốn trả về một con trỏ bằng cách tham chiếu. Ví dụ: nếu bạn có một lớp đang giữ dữ liệu trong một mảng được cấp phát động, nhưng bạn muốn cung cấp quyền truy cập (không quan trọng) vào dữ liệu này cho máy khách. Đồng thời, bạn không muốn máy khách có thể thao tác bộ nhớ thông qua con trỏ.

2
@William bạn có thể nói rõ hơn về điều đó không? Nghe thú vị. Nó có nghĩa là người dùng của lớp đó có thể nhận được một con trỏ đến bộ đệm dữ liệu bên trong và đọc / ghi vào nó, nhưng nó không thể - ví dụ - giải phóng bộ đệm đó bằng cách sử dụng deletehoặc gắn con trỏ đó vào một số vị trí khác trong bộ nhớ? Hay tôi đã hiểu sai?
BarbaraKwarc

2
@BarbaraKwarc Chắc chắn rồi. Giả sử bạn có một triển khai đơn giản của một mảng động. Đương nhiên bạn sẽ quá tải []nhà điều hành. Một chữ ký có thể (và trên thực tế là tự nhiên) cho điều này sẽ là const T &operator[](size_t index) const. Nhưng bạn cũng có thể có T &operator[](size_t index). Bạn có thể có cả hai cùng một lúc. Sau này sẽ cho phép bạn làm những việc như thế myArray[jj] = 42. Đồng thời, bạn không cung cấp một con trỏ tới dữ liệu của mình, vì vậy người gọi không thể làm xáo trộn bộ nhớ (ví dụ: vô tình xóa nó).

Oh. Tôi nghĩ rằng bạn đang nói về việc trả về một tham chiếu đến một con trỏ (từ ngữ chính xác của bạn: "trả về một con trỏ bằng tham chiếu", bởi vì đó là những gì OP đang hỏi về: chuyển con trỏ theo tham chiếu, không chỉ là tham chiếu cũ đơn thuần) như một cách ưa thích cho phép truy cập đọc / ghi vào bộ đệm mà không cho phép thao tác với chính bộ đệm (ví dụ: giải phóng nó). Trả lại một tham chiếu cũ đơn giản là một việc khác và tôi biết điều đó.
BarbaraKwarc

65

50% lập trình viên C ++ muốn đặt con trỏ của họ thành null sau khi xóa:

template<typename T>    
void moronic_delete(T*& p)
{
    delete p;
    p = nullptr;
}

Nếu không có tham chiếu, bạn sẽ chỉ thay đổi bản sao cục bộ của con trỏ, không ảnh hưởng đến người gọi.


19
Tôi thích cái tên; )
4pie0

2
Tôi sẽ giao dịch nghe có vẻ ngu ngốc khi tìm ra câu trả lời: tại sao nó được gọi là xóa moronic? Tôi đang thiếu gì?
Sammaron

1
@Sammaron Nếu bạn nhìn vào lịch sử, mẫu hàm từng được gọi paranoid_delete, nhưng Puppy đã đổi tên nó thành moronic_delete. Anh ta có lẽ thuộc về 50% còn lại;) Dù sao, câu trả lời ngắn gọn là con trỏ cài đặt đến null sau deletehầu như không bao giờ hữu ích (vì dù sao thì chúng cũng nằm ngoài phạm vi) và thường cản trở việc phát hiện các lỗi "sử dụng sau khi miễn phí".
fredoverflow

@fredoverflow Tôi hiểu phản hồi của bạn, nhưng nhìn chung @Puppy có vẻ cho deletelà ngu ngốc. Puppy, tôi đã thực hiện một số googling, tôi không thể hiểu tại sao xóa hoàn toàn vô dụng (có lẽ tôi cũng là một googler khủng khiếp;)). Bạn có thể giải thích thêm một chút hoặc cung cấp một liên kết?
Sammaron

@Sammaron, C ++ có "con trỏ thông minh" hiện đóng gói các con trỏ thông thường và tự động gọi "xóa" cho bạn khi chúng vượt ra khỏi phạm vi. Con trỏ thông minh được ưa thích hơn rất nhiều so với con trỏ câm kiểu C vì chúng loại bỏ hoàn toàn vấn đề này.
tjwrona1992

14

Câu trả lời của David là đúng, nhưng nếu nó vẫn còn hơi trừu tượng, đây là hai ví dụ:

  1. Bạn có thể muốn bằng không tất cả các con trỏ giải phóng để bắt các sự cố bộ nhớ sớm hơn. C-style bạn sẽ làm:

    void freeAndZero(void** ptr)
    {
        free(*ptr);
        *ptr = 0;
    }
    
    void* ptr = malloc(...);
    
    ...
    
    freeAndZero(&ptr);

    Trong C ++ để làm điều tương tự, bạn có thể làm:

    template<class T> void freeAndZero(T* &ptr)
    {
        delete ptr;
        ptr = 0;
    }
    
    int* ptr = new int;
    
    ...
    
    freeAndZero(ptr);
  2. Khi xử lý danh sách liên kết - thường được biểu diễn đơn giản dưới dạng con trỏ đến nút tiếp theo:

    struct Node
    {
        value_t value;
        Node* next;
    };

    Trong trường hợp này, khi bạn chèn vào danh sách trống, bạn nhất thiết phải thay đổi con trỏ đến vì kết quả không phải là NULLcon trỏ nữa. Đây là trường hợp bạn sửa đổi con trỏ bên ngoài từ một hàm, vì vậy nó sẽ có tham chiếu đến con trỏ trong chữ ký của nó:

    void insert(Node* &list)
    {
        ...
        if(!list) list = new Node(...);
        ...
    }

Có một ví dụ trong câu hỏi này .


8

Tôi đã phải sử dụng mã như thế này để cung cấp các chức năng cấp phát bộ nhớ cho một con trỏ được truyền vào và trả về kích thước của nó vì công ty của tôi "đối tượng" với tôi bằng cách sử dụng STL

 int iSizeOfArray(int* &piArray) {
    piArray = new int[iNumberOfElements];
    ...
    return iNumberOfElements;
 }

Nó không đẹp, nhưng con trỏ phải được truyền bằng tham chiếu (hoặc sử dụng con trỏ kép). Nếu không, bộ nhớ được cấp phát cho bản sao cục bộ của con trỏ nếu nó được truyền theo giá trị dẫn đến rò rỉ bộ nhớ.


2

Một ví dụ là khi bạn viết một hàm phân tích cú pháp và chuyển cho nó một con trỏ nguồn để đọc từ đó, nếu hàm được cho là đẩy con trỏ đó về phía trước phía sau ký tự cuối cùng đã được trình phân tích cú pháp nhận dạng chính xác. Việc sử dụng một tham chiếu đến một con trỏ sẽ làm rõ ràng rằng sau đó hàm sẽ di chuyển con trỏ ban đầu để cập nhật vị trí của nó.

Nói chung, bạn sử dụng tham chiếu đến con trỏ nếu bạn muốn chuyển con trỏ đến một hàm và để nó di chuyển con trỏ gốc đó đến một số vị trí khác thay vì chỉ di chuyển một bản sao của nó mà không ảnh hưởng đến bản gốc.


Tại sao lại ủng hộ? Có gì đó sai với những gì tôi đã viết? Quan tâm để giải thích? : P
BarbaraKwarc

0

Một tình huống khác khi bạn có thể cần điều này là nếu bạn có bộ sưu tập con trỏ stl và muốn thay đổi chúng bằng thuật toán stl. Ví dụ về for_each trong c ++ 98.

struct Storage {
  typedef std::list<Object*> ObjectList;
  ObjectList objects;

  void change() {
    typedef void (*ChangeFunctionType)(Object*&);
    std::for_each<ObjectList::iterator, ChangeFunctionType>
                 (objects.begin(), objects.end(), &Storage::changeObject);
  }

  static void changeObject(Object*& item) {
    delete item;
    item = 0;
    if (someCondition) item = new Object();
  } 

};

Ngược lại, nếu bạn sử dụng chữ ký changeObject (Object * item), bạn có bản sao của con trỏ, không phải bản gốc.


đây là thêm một "thay thế đối tượng" hơn là một "đối tượng thay đổi" - có sự khác biệt
serup
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.