เหตุผลในการส่งตัวชี้โดยการอ้างอิงใน C ++?


102

คุณต้องการใช้โค้ดลักษณะนี้ใน c ++ ภายใต้สถานการณ์ใด

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

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

หากคุณต้องการส่งคืนตัวชี้ - ควรใช้ค่าที่ส่งคืน
littleadv

6
คุณสามารถอธิบายเหตุผลได้หรือไม่? คำชมเชยนี้ไม่เป็นประโยชน์มากนัก คำถามของฉันค่อนข้างถูกกฎหมาย ขณะนี้กำลังใช้รหัสการผลิต ฉันไม่เข้าใจว่าทำไม
Matthew Hoggan

1
เดวิดสรุปได้ค่อนข้างดี ตัวชี้เองกำลังถูกแก้ไข
chris

2
ฉันผ่านวิธีนี้หากฉันจะเรียกตัวดำเนินการ "ใหม่" ในฟังก์ชัน
NDEthos

คำตอบ:


150

คุณต้องการส่งผ่านตัวชี้โดยการอ้างอิงหากคุณจำเป็นต้องแก้ไขตัวชี้แทนที่จะเป็นวัตถุที่ตัวชี้นั้นชี้ไป

ซึ่งคล้ายกับว่าทำไมถึงใช้พอยน์เตอร์คู่ การใช้ตัวชี้อ้างอิงจะปลอดภัยกว่าการใช้พอยน์เตอร์เล็กน้อย


14
คล้ายกับพอยน์เตอร์ของพอยน์เตอร์ยกเว้นความหมายที่น้อยกว่าหนึ่งระดับสำหรับความหมายแบบพาส - บาย - อ้างอิง?

ฉันต้องการเพิ่มว่าบางครั้งคุณต้องการส่งกลับตัวชี้โดยการอ้างอิง ตัวอย่างเช่นหากคุณมีคลาสที่เก็บข้อมูลไว้ในอาร์เรย์ที่จัดสรรแบบไดนามิก แต่คุณต้องการให้การเข้าถึงข้อมูลนี้กับไคลเอนต์ (ไม่คงที่) ในขณะเดียวกันคุณไม่ต้องการให้ไคลเอ็นต์สามารถจัดการหน่วยความจำผ่านตัวชี้ได้

2
@ วิลเลียมคุณสามารถอธิบายได้ไหม? ฟังดูน่าสนใจ หมายความว่าผู้ใช้ของคลาสนั้นสามารถรับตัวชี้ไปยังบัฟเฟอร์ข้อมูลภายในและอ่าน / เขียนได้ แต่ไม่สามารถ - ตัวอย่างเช่น - ปล่อยบัฟเฟอร์นั้นโดยใช้deleteหรือเชื่อมโยงตัวชี้นั้นไปยังที่อื่นในหน่วยความจำ หรือว่าฉันเข้าใจผิด?
BarbaraKwarc

2
@BarbaraKwarc ชัวร์. สมมติว่าคุณมีการใช้งานอาร์เรย์แบบไดนามิกอย่างง่าย โดยปกติคุณจะโอเวอร์โหลด[]โอเปอเรเตอร์ หนึ่งที่เป็นไปได้ (และในความเป็นจริงธรรมชาติ) const T &operator[](size_t index) constลายเซ็นนี้จะเป็น T &operator[](size_t index)แต่คุณยังสามารถมี คุณสามารถมีทั้งสองอย่างในเวลาเดียวกัน หลังจะให้คุณทำสิ่งต่างๆเช่นmyArray[jj] = 42. ในขณะเดียวกันคุณไม่ได้ให้ตัวชี้ไปยังข้อมูลของคุณดังนั้นผู้โทรจึงไม่สามารถยุ่งกับหน่วยความจำได้ (เช่นลบโดยไม่ได้ตั้งใจ)

โอ้. ฉันคิดว่าคุณกำลังพูดถึงการส่งคืนการอ้างอิงไปยังตัวชี้ (ข้อความที่ถูกต้องของคุณ: "return a pointer by reference" เพราะนั่นคือสิ่งที่ OP กำลังถาม: การส่งคำชี้โดยการอ้างอิงไม่ใช่แค่การอ้างอิงแบบเก่า ๆ ธรรมดา ๆ ) ซึ่งเป็นวิธีการ ให้สิทธิ์ในการอ่าน / เขียนไปยังบัฟเฟอร์โดยไม่อนุญาตให้จัดการบัฟเฟอร์เอง (เช่นการปล่อยบัฟเฟอร์) การย้อนกลับการอ้างอิงแบบเก่าเป็นสิ่งที่แตกต่างและฉันรู้
BarbaraKwarc

67

50% ของโปรแกรมเมอร์ C ++ ชอบตั้งค่าพอยน์เตอร์เป็นโมฆะหลังจากการลบ:

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

หากไม่มีการอ้างอิงคุณจะเปลี่ยนสำเนาของตัวชี้ในเครื่องเท่านั้นโดยไม่ส่งผลกระทบต่อผู้โทร


19
ฉันชอบชื่อ; )
4pie0

3
ฉันจะแลกเปลี่ยนฟังดูโง่ ๆ สำหรับการหาคำตอบ: ทำไมถึงเรียกว่าการลบแบบปัญญาอ่อน? ฉันขาดอะไรไป?
Sammaron

1
@Sammaron ถ้าคุณดูที่ประวัติความเป็นแม่แบบของฟังก์ชั่นที่ใช้ในการจะเรียกว่าแต่ลูกสุนัขมันเปลี่ยนไปparanoid_delete moronic_deleteเขาอาจเป็นของอีก 50%;) อย่างไรก็ตามคำตอบสั้น ๆ ก็คือการตั้งค่าที่ชี้ไปที่ค่าว่างหลังจากdeleteนั้นแทบจะไม่มีประโยชน์เลย (เพราะมันควรจะอยู่นอกขอบเขตอยู่ดี) และมักขัดขวางการตรวจจับข้อบกพร่อง "ใช้หลังจากฟรี"
fredoverflow

@fredoverflow ฉันเข้าใจคำตอบของคุณ แต่ @Puppy ดูเหมือนจะคิดว่าdeleteโง่โดยทั่วไป ลูกสุนัขฉันทำ googling ฉันไม่เห็นว่าทำไมการลบถึงไร้ประโยชน์โดยสิ้นเชิง (บางทีฉันก็เป็นคนโง่เหมือนกันนะ;)) คุณช่วยอธิบายเพิ่มเติมหรือให้ลิงค์ได้ไหม
Sammaron

@Sammaron, C ++ มี "ตัวชี้อัจฉริยะ" ในขณะนี้ที่ห่อหุ้มตัวชี้ปกติและเรียก "ลบ" ให้คุณโดยอัตโนมัติเมื่ออยู่นอกขอบเขต สมาร์ทพอยน์เตอร์เป็นที่ต้องการอย่างมากสำหรับพอยน์เตอร์ใบ้สไตล์ C เนื่องจากช่วยขจัดปัญหานี้ได้ทั้งหมด
tjwrona1992

15

คำตอบของ David นั้นถูกต้อง แต่ถ้ายังเป็นนามธรรมอยู่เล็กน้อยนี่คือตัวอย่างสองตัวอย่าง:

  1. คุณอาจต้องการให้ตัวชี้ที่เป็นอิสระทั้งหมดเป็นศูนย์เพื่อตรวจจับปัญหาหน่วยความจำก่อนหน้านี้ สไตล์ C ที่คุณทำ:

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

    ใน C ++ ให้ทำเช่นเดียวกันคุณอาจทำ:

    template<class T> void freeAndZero(T* &ptr)
    {
        delete ptr;
        ptr = 0;
    }
    
    int* ptr = new int;
    
    ...
    
    freeAndZero(ptr);
    
  2. เมื่อจัดการกับรายการที่เชื่อมโยง - มักแสดงเป็นเพียงตัวชี้ไปยังโหนดถัดไป:

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

    ในกรณีนี้เมื่อคุณแทรกในรายการว่างคุณจำเป็นต้องเปลี่ยนตัวชี้ขาเข้าเนื่องจากผลลัพธ์ไม่ใช่NULLตัวชี้อีกต่อไป นี่เป็นกรณีที่คุณแก้ไขตัวชี้ภายนอกจากฟังก์ชันดังนั้นจะมีการอ้างอิงถึงตัวชี้ในลายเซ็น:

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

มีตัวอย่างในคำถามนี้


8

ฉันต้องใช้รหัสแบบนี้เพื่อจัดเตรียมฟังก์ชันในการจัดสรรหน่วยความจำให้กับตัวชี้ที่ส่งผ่านและส่งคืนขนาดเนื่องจาก บริษัท ของฉัน "วัตถุ" ให้ฉันโดยใช้ STL

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

ไม่ดี แต่ต้องส่งตัวชี้โดยอ้างอิง (หรือใช้ตัวชี้คู่) หากไม่เป็นเช่นนั้นหน่วยความจำจะถูกจัดสรรให้กับสำเนาภายในของตัวชี้หากมีการส่งผ่านค่าซึ่งส่งผลให้เกิดการรั่วไหลของหน่วยความจำ


2

ตัวอย่างหนึ่งคือเมื่อคุณเขียนฟังก์ชันตัวแยกวิเคราะห์และส่งตัวชี้แหล่งที่มาเพื่ออ่านจากถ้าฟังก์ชันนั้นควรจะดันตัวชี้นั้นไปข้างหลังอักขระตัวสุดท้ายซึ่งตัววิเคราะห์ได้รับการยอมรับอย่างถูกต้อง การใช้การอ้างอิงตัวชี้ทำให้ชัดเจนจากนั้นฟังก์ชันจะย้ายตัวชี้เดิมเพื่ออัปเดตตำแหน่ง

โดยทั่วไปคุณใช้การอ้างอิงถึงตัวชี้หากคุณต้องการส่งตัวชี้ไปยังฟังก์ชันและปล่อยให้ตัวชี้เดิมนั้นย้ายไปยังตำแหน่งอื่นแทนการย้ายสำเนาโดยไม่ส่งผลกระทบต่อต้นฉบับ


ทำไมต้องโหวตลง? มีอะไรผิดปกติกับสิ่งที่ฉันเขียน? สนใจที่จะอธิบาย? : P
BarbaraKwarc

0

อีกสถานการณ์หนึ่งที่คุณอาจต้องการสิ่งนี้คือถ้าคุณมีคอลเลกชันของพอยน์เตอร์ stl และต้องการเปลี่ยนโดยใช้อัลกอริทึม stl ตัวอย่าง for_each ใน 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();
  } 

};

มิฉะนั้นหากคุณใช้ลายเซ็นchangeObject (Object * item)คุณมีสำเนาของตัวชี้ไม่ใช่ต้นฉบับ


นี่คือ "แทนที่วัตถุ" มากกว่า "เปลี่ยนวัตถุ" - มีความแตกต่าง
serup
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.