Cegah fungsi mengambil const std :: string & dari menerima 0

97

Senilai seribu kata:

#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;
}

Kompiler tidak mengeluh ketika meneruskan angka 0 ke operator braket menerima string. Sebaliknya, ini mengkompilasi dan gagal sebelum masuk ke metode dengan:

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

Sebagai referensi:

> 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)

Tebakanku

Kompiler secara implisit menggunakan std::string(0) konstruktor untuk memasukkan metode, yang menghasilkan masalah yang sama (google the error di atas) tanpa alasan yang bagus.

Pertanyaan

Apakah ada cara untuk memperbaikinya di sisi kelas, sehingga pengguna API tidak merasakan ini dan kesalahan terdeteksi pada waktu kompilasi?

Artinya, menambahkan kelebihan

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

bukan solusi yang baik.

kabanus
sumber
2
Dikompilasi kode, melempar pengecualian di Visual studio di ohNo [0] dengan pengecualian "0xC0000005: Akses membaca lokasi pelanggaran 0x00000000"
TruthSeeker
5
Nyatakan kelebihan pribadi operator[]()yang menerima intargumen, dan jangan mendefinisikannya.
Peter
2
@ Peter Meskipun perlu dicatat itu kesalahan linker , yang masih lebih baik dari apa yang saya miliki.
kabanus
5
@ kabanus Dalam skenario di atas, ini akan menjadi kesalahan kompiler , karena operator bersifat pribadi! Kesalahan tautan hanya jika dipanggil di dalam kelas ...
Aconcagua
5
@ Peter Itu sangat menarik dalam skenario di mana tidak ada C ++ 11 tersedia - dan ini memang ada bahkan hari ini (sebenarnya saya dalam proyek harus berurusan dengan, dan saya kehilangan cukup banyak beberapa fitur baru ... ).
Aconcagua

Jawaban:

161

Alasannya std::string(0)valid, adalah karena 0menjadi konstan pointer nol. Jadi 0 cocok dengan konstruktor string yang mengambil pointer. Kemudian kode berjalan bertabrakan dengan prasyarat bahwa seseorang tidak boleh melewatkan pointer nol std::string.

Hanya literal yang 0akan ditafsirkan sebagai konstanta penunjuk nol, jika itu adalah nilai waktu berjalan dalam intAnda tidak akan mengalami masalah ini (karena resolusi kelebihan beban akan mencari intkonversi sebagai gantinya). Secara literal juga 1bukan masalah, karena 1bukan merupakan pointer nol yang konstan.

Karena ini adalah masalah waktu kompilasi (nilai literal tidak valid) Anda dapat menangkapnya pada waktu kompilasi. Tambahkan kelebihan formulir ini:

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

std::nullptr_tadalah tipe nullptr. Dan itu akan cocok dengan setiap nol pointer konstan, baik itu 0, 0ULLatau nullptr. Dan karena fungsi ini dihapus, itu akan menyebabkan kesalahan waktu kompilasi selama resolusi kelebihan.

StoryTeller - Unslander Monica
sumber
Sejauh ini adalah solusi terbaik, benar-benar lupa saya bisa membebani pointer NULL.
kabanus
di Visual Studio, bahkan "ohNo [0]" melempar pengecualian nilai nol. Apakah ini berarti implementasi khusus dari std :: string class?
TruthSeeker
@ pmp Apa yang dilemparkan (jika ada) adalah implementasi khusus, tetapi intinya adalah string adalah pointer NULL di semua dari mereka. Dengan solusi ini Anda tidak akan sampai ke bagian pengecualian, itu akan terdeteksi pada waktu kompilasi.
kabanus
18
@pmp - Melewati pointer nol ke std::stringkonstruktor tidak diizinkan oleh standar C ++. Itu perilaku yang tidak terdefinisi, sehingga MSVC dapat melakukan apa pun yang disukainya (seperti melempar pengecualian).
StoryTeller - Unslander Monica
26

Salah satu opsi adalah untuk menyatakan privatekelebihanoperator[]() yang menerima argumen integral, dan jangan mendefinisikannya.

Opsi ini akan bekerja dengan semua standar C ++ (1998 aktif), tidak seperti opsi seperti void operator[](std::nullptr_t) = delete yang valid dari C ++ 11.

Membuat operator[]()sebuah privateanggota akan menyebabkan kesalahan didiagnosis pada contoh Anda ohNo[0], kecuali ungkapan yang digunakan oleh fungsi anggota ataufriend kelas.

Jika ungkapan itu digunakan dari fungsi anggota atau friendkelas, kode akan dikompilasi tetapi - karena fungsi tidak didefinisikan - umumnya build akan gagal (misalnya kesalahan linker karena fungsi yang tidak ditentukan).

Peter
sumber