Empêche la fonction de prendre const std :: string & d'accepter 0

97

Vaut mille mots:

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

Le compilateur ne se plaint pas en passant le numéro 0 à l'opérateur de parenthèse acceptant une chaîne. Au lieu de cela, cela compile et échoue avant l'entrée dans la méthode avec:

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

Pour référence:

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

Ma conjecture

Le compilateur utilise implicitement le std::string(0)constructeur pour entrer la méthode, ce qui génère le même problème (google l'erreur ci-dessus) sans raison valable.

Question

Existe-t-il un moyen de résoudre ce problème côté classe, afin que l'utilisateur de l'API ne le ressente pas et que l'erreur soit détectée au moment de la compilation?

Autrement dit, ajouter une surcharge

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

n'est pas une bonne solution.

kabanus
la source
2
Code compilé, levant une exception dans Visual studio sur ohNo [0] à l'exception "0xC0000005: emplacement de lecture de violation d'accès 0x00000000"
TruthSeeker
5
Déclarez une surcharge privée operator[]()qui accepte un intargument et ne le définissez pas.
Peter
2
@Peter Bien que notez qu'il s'agit d'une erreur de l' éditeur de liens , ce qui est toujours mieux que ce que j'avais.
kabanus
5
@kabanus Dans le scénario ci-dessus, ce sera une erreur de compilation , car l'opérateur est privé! Erreur de l'éditeur de liens uniquement s'il est appelé dans la classe ...
Aconcagua
5
@Peter C'est particulièrement intéressant dans les scénarios où aucun C ++ 11 n'est disponible - et ceux - ci existent même aujourd'hui (en fait, je suis dans un projet à gérer, et je manque à peu près certaines des nouvelles fonctionnalités ... ).
Aconcagua

Réponses:

161

La raison std::string(0)est valable, est due à 0être une constante de pointeur nul. Donc 0 correspond au constructeur de chaîne prenant un pointeur. Ensuite, le code va à l'encontre de la condition préalable à laquelle on ne peut pas passer un pointeur nul std::string.

Seul le littéral 0serait interprété comme une constante de pointeur nul, s'il s'agissait d'une valeur d'exécution dans un, intvous n'auriez pas ce problème (car alors la résolution de surcharge chercherait une intconversion à la place). Le littéral n'est pas non plus 1un problème, car il 1ne s'agit pas d'une constante de pointeur nul.

Puisqu'il s'agit d'un problème de temps de compilation (valeurs littérales invalides), vous pouvez l'attraper au moment de la compilation. Ajoutez une surcharge de ce formulaire:

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

std::nullptr_test le type de nullptr. Et il correspondra une constante de pointeur NULL, que ce soit 0, 0ULLou nullptr. Et puisque la fonction est supprimée, elle provoquera une erreur de temps de compilation pendant la résolution de surcharge.

Conteur - Unslander Monica
la source
C'est de loin la meilleure solution, j'ai complètement oublié que je peux surcharger un pointeur NULL.
kabanus
dans Visual Studio, même "ohNo [0]" lançant une exception de valeur nulle. Cela signifie-t-il une implémentation spécifique de la classe std :: string?
TruthSeeker
@pmp Ce qui est jeté (le cas échéant) est spécifique à l'implémentation, mais le fait est que la chaîne est un pointeur NULL dans chacun d'eux. Avec cette solution, vous n'obtiendrez pas la partie d'exception, elle sera détectée au moment de la compilation.
kabanus
18
@pmp - La transmission d'un pointeur nul au std::stringconstructeur de n'est pas autorisée par la norme C ++. C'est un comportement indéfini, donc MSVC peut faire ce qu'il veut (comme lever une exception).
Conteur - Unslander Monica
26

Une option consiste à déclarer une privatesurcharge operator[]()qui accepte un argument intégral et à ne pas le définir.

Cette option fonctionnera avec toutes les normes C ++ (à partir de 1998), contrairement aux options comme celles void operator[](std::nullptr_t) = deletequi sont valides à partir de C ++ 11.

La création d' operator[]()un privatemembre entraînera une erreur diagnostiquable sur votre exemple ohNo[0], sauf si cette expression est utilisée par une fonction membre ou friendde la classe.

Si cette expression est utilisée à partir d'une fonction membre ou friendde la classe, le code sera compilé mais - puisque la fonction n'est pas définie - généralement la construction échouera (par exemple une erreur de l'éditeur de liens due à une fonction non définie).

Peter
la source