Std :: string parametrelerine 0'dan en iyi nasıl korunulur?


20

Sadece rahatsız edici bir şey fark ettim. std::stringParamater olarak kabul eden bir yöntem her yazışımda, kendimi tanımlanmamış davranışa açtım.

Örneğin, bu ...

void myMethod(const std::string& s) { 
    /* Do something with s. */
}

... böyle denebilir ...

char* s = 0;
myMethod(s);

... ve bunu önlemek için yapabileceğim hiçbir şey yok (farkında olduğum).

Benim sorum şu: Birisi kendini bundan nasıl koruyor?

Akla gelen tek yaklaşım, her zaman std::stringbir parametre olarak kabul eden herhangi bir yöntemin iki versiyonunu yazmaktır , örneğin:

void myMethod(const std::string& s) {
    /* Do something. */
}

void myMethod(char* s) {
    if (s == 0) {
        throw std::exception("Null passed.");
    } else {
        myMethod(string(s));
    }
}

Bu yaygın ve / veya kabul edilebilir bir çözüm müdür?

DÜZENLEME: Bazıları parametre const std::string& syerine kabul etmem gerektiğini belirtti std::string s. Katılıyorum. Gönderiyi değiştirdim. Bunun cevabı değiştirdiğini sanmıyorum.


1
Sızdıran soyutlamalar için Yaşasın! C ++ geliştiricisi değilim, ancak dize nesnesinin c_strözelliğini kontrol edememenizin herhangi bir nedeni var mı?
Mason Wheeler

6
char * yapıcısına 0 atamak tanımsız bir davranış, bu yüzden arayanlar gerçekten hata
cırcır ucube

4
@ratchetfreak Bunun char* s = 0tanımsız olduğunu bilmiyordum . Hayatımda (genellikle şeklinde char* s = NULL) en az birkaç yüz kez gördüm . Bunu destekleyecek bir referansınız var mı?
John Fitzpatrick

3
Ben std:string::string(char*)yapıcı kastediyorum
cırcır ucube

2
Bence çözümünüz iyi, ama hiçbir şey yapmamayı düşünmelisiniz. :-) Metodunuz oldukça açık bir şekilde bir dize alıyor, hiçbir şekilde geçerli bir eylem olarak adlandırılırken boş bir işaretçi geçmiyor - bir arayanın yanlışlıkla bu tür yöntemlere nulls vurması durumunda (daha doğrusu) örneğin bir günlük dosyasında raporlanmaktan daha iyidir). Eğer , o zaman yapmalıyım derleme sırasında bu şey önlemek için bir yol vardı yoksa ben bırakırdım. BENİM NACİZANE FİKRİME GÖRE. (BTW, const std::string&bu parametre için bir alamadığınızdan emin misiniz ...?)
Grimm Opiner

Yanıtlar:


21

Kendinizi korumanız gerektiğini düşünmüyorum. Arayan tarafında tanımsız davranış. Sen değilsin, arayan arayan std::string::string(nullptr), izin verilmeyen şey bu. Derleyici derlenmesine izin verir, ancak diğer tanımlanmamış davranışların da derlenmesine izin verir.

Aynı şekilde "boş başvuru" elde edilir:

int* p = nullptr;
f(*p);
void f(int& x) { x = 0; /* bang! */ }

Boş göstericiyi kaydeden kişi UB yapıyor ve bundan sorumlu.

Üstelik sen olamaz iyileştirici tanımsız davranış hiç olmamış farz tam sağ olduğu için eğer tanımsız davranış, böylece kontrolleri oldu sonra kendinizi korumak c_str()boş dışarı optimize edilebilir olduğunu.


Aynı şeyi söyleyen güzel yazılmış bir yorum var, bu yüzden doğru olmalısın. ;-)
Grimm The Opiner

2
Buradaki ifade Machiavelli'ye değil Murphy'ye karşı korumaktır. İyi huylu bir kötü niyetli programcı, yeterince çalıştıklarında null referanslar oluşturmak için kötü şekillendirilmiş nesneleri göndermenin yollarını bulabilir, ancak bu kendi işidir ve gerçekten istedikleri takdirde kendilerini ayaklarından vurmalarına izin verebilirsiniz. Yapmanız beklenebilecek en iyi şey, yanlışlıkla hataları önlemektir. Bu durumda, birisinin yanlışlıkla bir char * s = 0 değerini geçmesi nadirdir; iyi biçimlendirilmiş bir dize isteyen bir işleve.
YoungJohn

2

Aşağıdaki kod, açık geçişi için derleme hatası 0ve değeri olan bir çalışma zamanı hatası char*verir0 .

Birinin normalde bunu yapması gerektiğini ima etmiyorum, ancak şüphesiz arayan hatasından korumanın haklı olduğu durumlar olabilir.

struct Test
{
    template<class T> void myMethod(T s);
};

template<> inline void Test::myMethod(const std::string& s)
{
    std::cout << "Cool " << std::endl;
}

template<> inline void Test::myMethod(const char* s)
{
    if (s != 0)
        myMethod(std::string(s));
    else
    {
        throw "Bad bad bad";
    }
}

template<class T> inline void Test::myMethod(T  s)
{
    myMethod(std::string(s));
    const bool ok = !std::is_same<T,int>::value;
    static_assert(ok, "oops");
}

int main()
{
    Test t;
    std::string s ("a");
    t.myMethod("b");
    const char* c = "c";
    t.myMethod(c);
    const char* d = 0;
    t.myMethod(d); // run time exception
    t.myMethod(0); // Compile failure
}

1

Birkaç yıl önce bu problemle de karşılaştım ve gerçekten çok korkutucu bir şey buldum. Bir nullptrdeğeri geçerek veya yanlışlıkla 0 değerine sahip bir int geçirerek olabilir. Gerçekten saçma:

std::string s(1); // compile error
std::string s(0); // runtime error

Ancak, sonunda bu sadece birkaç kez beni rahatsız etti. Ve her zaman kodumu test ederken hemen bir çökmeye neden oldu. Bu nedenle, düzeltmek için gece boyunca oturum gerekmez.

Bence fonksiyonu aşırı yüklemek const char*iyi bir fikir.

void foo(std::string s)
{
    // work
}

void foo(const char* s) // use const char* rather than char* (binds to string literals)
{
    assert(s); // I would use assert or std::abort in case of nullptr . 
    foo(std::string(s));
}

Keşke daha güzel bir çözüm mümkün olsaydı. Ancak, yoktur.


2
Ancak, ne zaman için çalışma zamanı hatası alıyorsunuz foo(0)ve hata derleyinfoo(1)
Bryan Chen

1

Yönteminizin imzasını şu şekilde değiştirmeye ne dersiniz?

void myMethod(std::string& s) // maybe throw const in there too.

Bu şekilde arayanın, onu çağırmadan önce bir dize oluşturması gerekir ve endişelendiğiniz özensizlik, yönteminize ulaşmadan önce sorunlara neden olur ve diğerlerinin işaret ettiklerini, sizin değil, arayanın hatası olduğunu açıklığa kavuşturur.


evet yapmalıydım const string& s, aslında unuttum. Ama buna rağmen, hala tanımsız davranışlara karşı savunmasız değil miyim? Arayan yine de geçebilir 0, değil mi?
John Fitzpatrick

2
Sabit olmayan bir başvuru kullanırsanız, geçici nesnelere izin verilmeyeceğinden arayan artık 0 değerini geçemez. Ancak, kitaplığınızı kullanmak çok daha can sıkıcı bir hale gelecektir (çünkü geçici nesnelere izin verilmeyecektir) ve siz const doğruluğundan vazgeçiyorsunuz.
Josh Kelley

Bir keresinde onu saklamak zorunda olduğu bir sınıfa const olmayan bir başvuru parametresi kullandım ve bu nedenle herhangi bir dönüşüm veya geçici geçirilmesi yoktu.
yapılmadığından emin oldum

1

Aşırı yükte bir intparametreyi nasıl alırsınız ?

public:
    void myMethod(const std::string& s)
    { 
        /* Do something with s. */
    }    

private:
    void myMethod(int);

Aşırı yüklemeyi tanımlamanız bile gerekmez. Aramaya çalışmak myMethod(0)bir bağlayıcı hatası tetikleyecektir.


1
Bu soruya, kod karşı koruma sağlamaz 0bir sahiptir char*türü.
Ben Voigt

0

A ile çağırmayı denerseniz, ilk kod bloğundaki yönteminiz asla çağrılmaz (char *)0. C ++ basitçe bir dize oluşturmaya çalışır ve sizin için istisna atar. Onu denedin mi?

#include <cstdlib>
#include <iostream>

void myMethod(std::string s) {
    std::cout << "s=" << s << "\n";
}

int main(int argc,char **argv) {
    char *s = 0;
    myMethod(s);
    return(0);
}


$ g++ -g -o x x.cpp 
$ lldb x 
(lldb) run
Process 2137 launched: '/Users/simsong/x' (x86_64)
Process 2137 stopped
* thread #1: tid = 0x49b8, 0x00007fff99bf9812 libsystem_c.dylib`strlen + 18, queue = 'com.apple.main-thread, stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x00007fff99bf9812 libsystem_c.dylib`strlen + 18
libsystem_c.dylib`strlen + 18:
-> 0x7fff99bf9812:  pcmpeqb (%rdi), %xmm0
   0x7fff99bf9816:  pmovmskb %xmm0, %esi
   0x7fff99bf981a:  andq   $15, %rcx
   0x7fff99bf981e:  orq    $-1, %rax
(lldb) bt
* thread #1: tid = 0x49b8, 0x00007fff99bf9812 libsystem_c.dylib`strlen + 18, queue = 'com.apple.main-thread, stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x00007fff99bf9812 libsystem_c.dylib`strlen + 18
    frame #1: 0x000000010000077a x`main [inlined] std::__1::char_traits<char>::length(__s=0x0000000000000000) + 122 at string:644
    frame #2: 0x000000010000075d x`main [inlined] std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::basic_string(this=0x00007fff5fbff548, __s=0x0000000000000000) + 8 at string:1856
    frame #3: 0x0000000100000755 x`main [inlined] std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::basic_string(this=0x00007fff5fbff548, __s=0x0000000000000000) at string:1857
    frame #4: 0x0000000100000755 x`main(argc=1, argv=0x00007fff5fbff5e0) + 85 at x.cpp:10
    frame #5: 0x00007fff92ea25fd libdyld.dylib`start + 1
(lldb) 

Görmek? Endişelenecek bir şeyin yok.

Şimdi bunu biraz daha zarif bir şekilde yakalamak istiyorsanız, sadece kullanmamalısınız char *, o zaman sorun ortaya çıkmaz.


4
nullpointer ile bir std :: string oluşturmak tanımsız bir davranış olacaktır
cırcır ucube

2
EXC_BAD_ACCESS istisnası, programınızı iyi bir günde bir segfault ile kilitleyecek boş bir dereference gibi geliyor
cırcır ucube

@ vy32 Kabul ettiğim bazı kodlar std::string, arayan olmadığım diğer projeler tarafından kullanılan kütüphanelere gidiyor. Durumu incelikle ele almak ve arayanı (belki de bir istisna ile) programın çökmesine neden olmadan yanlış bir argüman geçirdiğini bilgilendirmek için bir yol arıyorum. (Tamam, kabul edildi, arayan attığım bir istisnayı karşılamayabilir ve program yine de çökecektir.)
John Fitzpatrick

2
@JohnFitzpatrick, std :: string'e geçirilmiş bir boş göstericiden kendinizi koruyamayacaksınız, standartların tanımlanmamış davranış yerine bir istisna olmasını ikna edemediğiniz sürece
cırcır ucube

@ratchetfreak Bir bakıma aradığım cevap bu. Temel olarak kendimi korumam gerekiyor.
John Fitzpatrick

0

Char * 'ın potansiyel olarak boş göstergeler (örneğin, harici C API'lerinden döndürme) olması konusunda endişeleriniz varsa, sorunun işlevi std :: string one yerine const char * sürümünü kullanmaktır. yani

void myMethod(const char* c) { 
    std::string s(c ? c : "");
    /* Do something with s. */
}

Tabii bunların kullanılmasına izin vermek istiyorsanız std :: string versiyonuna da ihtiyacınız olacak.

Genel olarak, harici API'lere ve mareşal argümanlarına yapılan çağrıları geçerli değerlere ayırmak en iyisidir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.