พื้นฐานของ nullptr
std::nullptr_t
เป็นชนิดของตัวชี้ null ตัวอักษร nullptr มันเป็น prvalue / rvalue std::nullptr_t
ประเภท มีการแปลงโดยนัยจาก nullptr เป็นค่าตัวชี้ null ของประเภทตัวชี้ใด ๆ
ตัวอักษร 0 เป็น int ไม่ใช่ตัวชี้ ถ้า C ++ พบว่าตัวเองกำลังมองหา 0 ในบริบทที่สามารถใช้ตัวชี้ได้เท่านั้นมันจะตีความว่า 0 เป็นตัวชี้โมฆะอย่างไม่น่าเชื่อ แต่นั่นคือตำแหน่งทางเลือก นโยบายหลักของ C ++ คือ 0 คือ int ไม่ใช่ตัวชี้
ข้อได้เปรียบ 1 - ลบความคลุมเครือเมื่อมีการใช้งานมากเกินตัวชี้และส่วนประกอบ
ใน C ++ 98 ความหมายหลักของเรื่องนี้คือการโอเวอร์โหลดบนตัวชี้และประเภทอินทิกรัลอาจนำไปสู่ความประหลาดใจ การผ่าน 0 หรือ NULL ไปยังโอเวอร์โหลดดังกล่าวไม่เคยเรียกว่าตัวชี้โอเวอร์โหลด:
void fun(int); // two overloads of fun
void fun(void*);
fun(0); // calls f(int), not fun(void*)
fun(NULL); // might not compile, but typically calls fun(int). Never calls fun(void*)
สิ่งที่น่าสนใจเกี่ยวกับการโทรนั้นคือความขัดแย้งระหว่างความหมายที่ชัดเจนของซอร์สโค้ด (“ ฉันกำลังเรียกความสนุกกับตัวชี้ NULL-the null”) และความหมายที่แท้จริงของมัน ชี้”)
ข้อได้เปรียบของ nullptr คือมันไม่มีประเภทที่สมบูรณ์ การเรียกฟังก์ชั่นโอเวอร์โหลดด้วยความสนุกด้วย nullptr จะเรียกโมฆะ * overload (เช่นตัวชี้โอเวอร์โหลด) เนื่องจาก nullptr ไม่สามารถดูเป็นส่วนสำคัญได้:
fun(nullptr); // calls fun(void*) overload
ใช้ nullptr แทน 0 หรือ NULL จึงหลีกเลี่ยงความประหลาดใจในการแก้ปัญหาการโอเวอร์โหลด
ประโยชน์ของผู้อื่นnullptr
มากกว่าNULL(0)
เมื่อใช้รถยนต์ประเภทกลับ
ตัวอย่างเช่นสมมติว่าคุณพบสิ่งนี้ในฐานรหัส:
auto result = findRecord( /* arguments */ );
if (result == 0) {
....
}
หากคุณไม่ทราบ (หรือไม่สามารถหาได้ง่าย) สิ่งที่ findRecord ส่งคืนอาจไม่ชัดเจนว่าผลลัพธ์เป็นชนิดตัวชี้หรือชนิดอินทิกรัล ท้ายที่สุด 0 (สิ่งที่ทดสอบกับผลลัพธ์) สามารถไปทางใด หากคุณเห็นสิ่งต่อไปนี้ในทางกลับกัน
auto result = findRecord( /* arguments */ );
if (result == nullptr) {
...
}
ไม่มีความกำกวม: ผลลัพธ์จะต้องเป็นประเภทพอยน์เตอร์
ข้อได้เปรียบ 3
#include<iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
int f1(std::shared_ptr<int> spw) // call these only when
{
//do something
return 0;
}
double f2(std::unique_ptr<int> upw) // the appropriate
{
//do something
return 0.0;
}
bool f3(int* pw) // mutex is locked
{
return 0;
}
std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3
using MuxtexGuard = std::lock_guard<std::mutex>;
void lockAndCallF1()
{
MuxtexGuard g(f1m); // lock mutex for f1
auto result = f1(static_cast<int>(0)); // pass 0 as null ptr to f1
cout<< result<<endl;
}
void lockAndCallF2()
{
MuxtexGuard g(f2m); // lock mutex for f2
auto result = f2(static_cast<int>(NULL)); // pass NULL as null ptr to f2
cout<< result<<endl;
}
void lockAndCallF3()
{
MuxtexGuard g(f3m); // lock mutex for f2
auto result = f3(nullptr);// pass nullptr as null ptr to f3
cout<< result<<endl;
} // unlock mutex
int main()
{
lockAndCallF1();
lockAndCallF2();
lockAndCallF3();
return 0;
}
ข้างต้นโปรแกรมรวบรวมและดำเนินการสำเร็จ แต่ lockAndCallF1, lockAndCallF2 & lockAndCallF3 มีรหัสซ้ำซ้อน มันน่าเสียดายที่จะเขียนโค้ดแบบนี้ถ้าเราสามารถเขียนเทมเพลตสำหรับสิ่งเหล่านี้lockAndCallF1, lockAndCallF2 & lockAndCallF3
ได้ ดังนั้นจึงสามารถวางนัยกับแม่แบบ ฉันได้เขียนฟังก์ชั่นเทมเพลตlockAndCall
แทนคำจำกัดความหลายคำlockAndCallF1, lockAndCallF2 & lockAndCallF3
สำหรับรหัสซ้ำซ้อน
รหัสจะถูกแยกออกเป็นส่วน ๆ ดังต่อไปนี้:
#include<iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
int f1(std::shared_ptr<int> spw) // call these only when
{
//do something
return 0;
}
double f2(std::unique_ptr<int> upw) // the appropriate
{
//do something
return 0.0;
}
bool f3(int* pw) // mutex is locked
{
return 0;
}
std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3
using MuxtexGuard = std::lock_guard<std::mutex>;
template<typename FuncType, typename MuxType, typename PtrType>
auto lockAndCall(FuncType func, MuxType& mutex, PtrType ptr) -> decltype(func(ptr))
//decltype(auto) lockAndCall(FuncType func, MuxType& mutex, PtrType ptr)
{
MuxtexGuard g(mutex);
return func(ptr);
}
int main()
{
auto result1 = lockAndCall(f1, f1m, 0); //compilation failed
//do something
auto result2 = lockAndCall(f2, f2m, NULL); //compilation failed
//do something
auto result3 = lockAndCall(f3, f3m, nullptr);
//do something
return 0;
}
การวิเคราะห์รายละเอียดว่าทำไมการรวบรวมล้มเหลวlockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
ไม่ใช่เพราะlockAndCall(f3, f3m, nullptr)
ทำไมการรวบรวมlockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
ล้มเหลว?
ปัญหาคือเมื่อ 0 ถูกส่งผ่านไปยัง lockAndCall การลดประเภทเทมเพลตจะเริ่มขึ้นเพื่อหาประเภท ชนิดของ 0 คือ int ดังนั้นจึงเป็นชนิดของพารามิเตอร์ ptr ภายในการสร้างอินสแตนซ์ของการเรียกนี้เพื่อ lockAndCall น่าเสียดายที่นี่หมายความว่าในการเรียกไปยัง func ภายใน lockAndCall int จะถูกส่งผ่านและไม่เข้ากันได้กับstd::shared_ptr<int>
พารามิเตอร์ที่f1
คาดหวัง 0 ที่ส่งผ่านไปยังการโทรไปlockAndCall
นั้นมีจุดประสงค์เพื่อแสดงตัวชี้โมฆะ แต่สิ่งที่ได้รับการส่งผ่านจริงคือ int พยายามส่งค่า int นี้ไปยัง f1 เนื่องจาก a std::shared_ptr<int>
เป็นข้อผิดพลาดประเภท การโทรไปlockAndCall
มี 0 ล้มเหลวเพราะภายในแม่แบบเป็น int std::shared_ptr<int>
จะถูกส่งผ่านไปยังฟังก์ชั่นที่ต้องใช้เป็น
การวิเคราะห์การโทรเกี่ยวข้องNULL
เป็นหลักเดียวกัน เมื่อNULL
ถูกส่งผ่านไปยังlockAndCall
ประเภทหนึ่งจะถูกอนุมานสำหรับพารามิเตอร์ ptr และข้อผิดพลาดประเภทเกิดขึ้นเมื่อptr
- ประเภท int หรือเหมือน int - ถูกส่งผ่านไปf2
ซึ่งคาดว่าจะได้รับstd::unique_ptr<int>
ซึ่งคาดว่าจะได้รับ
ในทางตรงกันข้ามการโทรที่เกี่ยวข้องnullptr
ไม่มีปัญหา เมื่อnullptr
ถูกส่งไปยังlockAndCall
ประเภทสำหรับจะสรุปได้ว่าจะเป็นptr
std::nullptr_t
เมื่อptr
ถูกส่งผ่านไปf3
ยังมีการแปลงโดยนัยจากstd::nullptr_t
เป็นint*
เพราะstd::nullptr_t
แปลงโดยอ้อมไปเป็นตัวชี้ทุกประเภท
ก็จะแนะนำเมื่อใดก็ตามที่คุณต้องการที่จะอ้างถึงตัวชี้โมฆะ, nullptr การใช้งานไม่เป็น 0 NULL
หรือ
int
และvoid *
จะไม่เลือกint
รุ่นมากกว่ารุ่นเมื่อใช้void *
nullptr