Запрет функции, принимающей 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");
}

не очень хорошее решение

kabanus
источник
2
Скомпилированный код, выдающий исключение в Visual Studio в ohNo [0] с исключением «0xC0000005: место чтения нарушения прав доступа 0x00000000»
TruthSeeker
5
Объявите частную перегрузку, operator[]()которая принимает intаргумент, и не определяйте его.
Питер
2
@Peter Хотя заметьте, это ошибка компоновщика , которая все же лучше, чем у меня.
Кабанус
5
@kabanus В приведенном выше сценарии это будет ошибка компилятора , потому что оператор является частным! Ошибка компоновщика, только если вызывается в классе ...
Аконкагуа
5
@Peter Это особенно интересно в ситуациях , когда нет C ++ 11 не доступно - и они действительно существуют даже сегодня ( на самом деле я в проекте, чтобы иметь дело с, и я пропускаю в значительной степени некоторые из новых возможностей ... ).
Аконкагуа

Ответы:

161

Причина std::string(0)действительна, потому 0что является константой нулевого указателя. Таким образом, 0 соответствует строковому конструктору, принимающему указатель. Затем код запускается из-за предусловия, на которое нельзя передавать нулевой указатель std::string.

Только литерал 0будет интерпретироваться как константа нулевого указателя, если бы это было значение времени выполнения, то у intвас не было бы этой проблемы (потому что тогда при разрешении перегрузки intвместо этого нужно было бы искать преобразование). И не является литералом 1проблема, потому что 1не является константой нулевого указателя.

Поскольку это проблема времени компиляции (буквальные недопустимые значения), вы можете поймать ее во время компиляции. Добавьте перегрузку этой формы:

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

std::nullptr_tэто тип nullptr. И это будет соответствовать любой постоянной нулевой указатель, будь то 0, 0ULLилиnullptr . А так как функция удалена, она вызовет ошибку времени компиляции во время разрешения перегрузки.

Рассказчик - Unslander Monica
источник
На сегодняшний день это лучшее решение, совершенно забыл, что я могу перегрузить нулевой указатель.
Кабанус
в Visual Studio даже "ohNo [0]" выдает исключение с нулевым значением. Означает ли это конкретную реализацию класса std :: string?
Истинный Искатель
@pmp То, что выдается (если что-то), зависит от реализации, но смысл в том, что строка является пустым указателем во всех из них. С этим решением вы не попадете в исключительную часть, она будет обнаружена во время компиляции.
Кабанус
18
@pmp. Передача нулевого указателя в std::stringконструктор не допускается стандартом C ++. Это неопределенное поведение, поэтому MSVC может делать все что угодно (например, генерировать исключение).
StoryTeller - Unslander Моника
26

Одним из вариантов является объявление privateперегрузкиoperator[]() которая принимает интегральный аргумент, и не определять его.

Эта опция будет работать со всеми стандартами C ++ (с 1998 года), в отличие от таких опций, как void operator[](std::nullptr_t) = delete начиная с которые действительны в C ++ 11.

Создание operator[]()в privateэлементе вызовет диагностируемую ошибку на вашем примере ohNo[0], если это выражение не используются в функции члена илиfriend класса.

Если это выражение используется из функции-члена или friendкласса, код будет компилироваться, но - поскольку функция не определена - обычно сборка завершится неудачно (например, ошибка компоновщика из-за неопределенной функции).

Питер
источник