ป้องกันไม่ให้ฟังก์ชันรับ const std :: string & จากการยอมรับ 0


97

มีค่าหนึ่งพันคำ:

#include<string>
#include<iostream>

class SayWhat {
    public:
    SayWhat& operator[](const std::string& s) {
        std::cout<<"here\n"; // To make sure we fail on function entry
        std::cout<<s<<"\n";
        return *this;
    }
};

int main() {
    SayWhat ohNo;
    // ohNo[1]; // Does not compile. Logic prevails.
    ohNo[0]; // you didn't! this compiles.
    return 0;
}

คอมไพเลอร์ไม่ได้บ่นเมื่อผ่านหมายเลข 0 ไปยังผู้ประกอบการวงเล็บยอมรับสตริง แต่จะรวบรวมและล้มเหลวก่อนที่จะเข้าสู่วิธีการด้วย:

terminate called after throwing an instance of 'std::logic_error'
  what():  basic_string::_S_construct null not valid

สำหรับการอ้างอิง:

> g++ -std=c++17 -O3 -Wall -Werror -pedantic test.cpp -o test && ./test
> g++ --version
gcc version 7.3.1 20180303 (Red Hat 7.3.1-5) (GCC)

ฉันเดา

คอมไพเลอร์ใช้ปริยายในการstd::string(0)สร้างวิธีการซึ่งทำให้เกิดปัญหาเดียวกัน (google ข้อผิดพลาดข้างต้น) โดยไม่มีเหตุผลที่ดี

คำถาม

อย่างไรก็ตามมีการแก้ไขปัญหานี้ในด้านคลาสดังนั้นผู้ใช้ API ไม่รู้สึกว่าสิ่งนี้และตรวจพบข้อผิดพลาดในเวลารวบรวม?

นั่นคือการเพิ่มเกินพิกัด

void operator[](size_t t) {
    throw std::runtime_error("don't");
}

ไม่ใช่ทางออกที่ดี


2
รหัสรวบรวม, การโยนข้อยกเว้นใน Visual Studio ที่ ohNo [0] โดยมีข้อยกเว้น "0xC0000005: การเข้าถึงการอ่านการละเมิดตำแหน่ง 0x00000000"
TruthSeeker

5
ประกาศโอเวอร์โหลดส่วนตัวของoperator[]()ที่ยอมรับการintโต้แย้งและไม่ได้กำหนดไว้
ปีเตอร์

2
@ Peter แม้ว่าจะทราบว่าเป็นข้อผิดพลาดlinkerซึ่งยังดีกว่าสิ่งที่ฉันมี
kabanus

5
@kabanus ในสถานการณ์ข้างต้นมันจะเป็นข้อผิดพลาดในการคอมไพเลอร์เพราะผู้ประกอบการเป็นส่วนตัว! ข้อผิดพลาด Linker เฉพาะในกรณีที่เรียกว่าในชั้นเรียน ...
Aconcagua

5
@ Peter เป็นเรื่องที่น่าสนใจอย่างยิ่งในสถานการณ์ที่ไม่มี C ++ 11 - และสิ่งเหล่านี้ยังมีอยู่ในทุกวันนี้ (จริงๆแล้วฉันอยู่ในโครงการที่ต้องจัดการกับและฉันก็ขาดคุณสมบัติใหม่บางอย่าง ... )
Aconcagua

คำตอบ:


161

เหตุผลที่std::string(0)ถูกต้องเนื่องจาก0เป็นค่าคงที่ตัวชี้โมฆะ ดังนั้น 0 ตรงกับตัวสร้างสตริงที่ใช้ตัวชี้ จากนั้นโค้ดจะวิ่งไปตามเงื่อนไขที่ไม่อาจผ่านพอยน์เตอร์ไปstd::stringได้

ตัวอักษรเท่านั้นที่0จะถูกตีความว่าเป็นค่าคงที่ตัวชี้โมฆะถ้ามันเป็นค่าเวลาทำงานในintคุณจะไม่มีปัญหานี้ (เพราะแล้วความละเอียดเกินพิกัดจะมองหาการintแปลงแทน) หรือเป็น1ปัญหาตามตัวอักษรเพราะ1ไม่ใช่ค่าคงที่ตัวชี้โมฆะ

เนื่องจากเป็นปัญหาเวลารวบรวม (ค่าไม่ถูกต้องตามตัวอักษร) คุณสามารถจับมันได้ในเวลารวบรวม เพิ่มการโอเวอร์โหลดของแบบฟอร์มนี้:

void operator[](std::nullptr_t) = delete;

std::nullptr_tnullptrเป็นประเภทของ และมันก็จะตรงกับการใด ๆคงชี้โมฆะไม่ว่าจะเป็น0, หรือ0ULL nullptrและเนื่องจากฟังก์ชั่นจะถูกลบมันจะทำให้เกิดข้อผิดพลาดเวลาในการรวบรวมในระหว่างการแก้ปัญหาการโอเวอร์โหลด


นี่คือทางออกที่ดีที่สุดโดยลืมว่าฉันสามารถใช้ตัวชี้ NULL มากเกินไปได้
kabanus

ใน Visual Studio แม้ "ohNo [0]" การขว้างข้อยกเว้นค่า null มันหมายถึงการใช้งานเฉพาะของ std :: string class?
TruthSeeker

@pmp มีอะไรโยน (ถ้ามี) การใช้งานที่เฉพาะเจาะจง แต่จุดคือสตริงที่เป็นตัวชี้ NULL ในพวกเขาทั้งหมด ด้วยวิธีนี้คุณจะไม่ได้รับการยกเว้นส่วนมันจะถูกตรวจพบในเวลารวบรวม
kabanus

18
@pmp - การส่งตัวชี้ null ไปยังตัวstd::stringสร้างของไม่ได้รับอนุญาตโดยมาตรฐาน C ++ มันเป็นพฤติกรรมที่ไม่ได้กำหนดดังนั้น MSVC สามารถทำสิ่งที่มันชอบ (เช่นโยนข้อยกเว้น)
StoryTeller - Unslander Monica

26

ทางเลือกหนึ่งคือการประกาศprivateโอเวอร์โหลดoperator[]()ที่ยอมรับอาร์กิวเมนต์ที่สำคัญและไม่ได้กำหนด

ตัวเลือกนี้จะทำงานกับมาตรฐาน C ++ ทั้งหมด (1998 เป็นต้นไป) ซึ่งแตกต่างจากตัวเลือกvoid operator[](std::nullptr_t) = deleteที่ใช้ได้จาก C ++ 11

ทำสมาชิกจะทำให้เกิดข้อผิดพลาดในการตรวจวินิจฉัยได้ตัวอย่างของคุณ เว้นแต่การแสดงออกที่ถูกใช้โดยฟังก์ชันสมาชิกหรือของชั้นเรียนoperator[]()privateohNo[0]friend

หากการแสดงออกนั้นถูกใช้จากฟังก์ชั่นสมาชิกหรือfriendของคลาสรหัสจะรวบรวม แต่ - เนื่องจากฟังก์ชั่นไม่ได้กำหนด - โดยทั่วไปการสร้างจะล้มเหลว (เช่นข้อผิดพลาด linker เนื่องจากฟังก์ชั่นที่ไม่ได้กำหนด)

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