Как избежать частой ошибки в большой кодовой базе?

Есть ли способ отменить определение + = в строках и wstrings для символов и wchar_t?

В основном я хочу избежать таких ошибок:

int age = 27;

std::wstring str = std::wstring(L"User's age is: ");
str += age;

std::string str2 = std::string("User's age is: ");
str2 += age;

Приведенный выше код добавит в строку символ ascii 27 вместо числа 27.

Я, очевидно, знаю, как это исправить, но у меня вопрос: как в этой ситуации вызвать ошибку компилятора?

Примечание. Вы можете переопределить + = для std :: string и int, чтобы правильно отформатировать строку, но это не то, что я хочу делать. Я хочу полностью запретить этот оператор для этих операндов.

Я бы сказал, что это не проблема. всегда можно найти способ прострелить себе ногу. Лучше научите коллег правильно программировать на C++ :)

Johannes Schaub - litb 05.01.2009 10:20

Выстрелить себе в ногу неизбежен. Таким образом, вы должны кодировать, чтобы убедиться, что невозможно выстрелить себе в ногу. В противном случае у вас будет ужасный код.

Brian R. Bondy 05.01.2009 10:43

«Выстрелить себе в ногу неизбежен. Поэтому вы должны кодировать, чтобы убедиться, что невозможно выстрелить себе в ногу». Эти утверждения противоречат друг другу :) И стрельба ногами - самый быстрый путь к тому, чтобы стать лучшим программистом, если вы учитесь на своих ошибках.

user19302 05.01.2009 12:07

Они не противоречат друг другу. Первое заявление было комментарием к ответу litb. Во втором утверждении говорилось, что если у вас есть способ что-то испортить, используя миллионы строк кода, вы в конечном итоге это сделаете. Так кодируйте лучше и сделайте это невозможным, например, сгенерировав ошибку компиляции.

Brian R. Bondy 07.01.2009 16:03

Например: вы можете передать все свои указатели как void *, а затем C-привести их обратно к исходному типу после того, как вы войдете в функцию. Это пример того, что вам не следует делать, потому что в конечном итоге вы выстрелите себе в ногу.

Brian R. Bondy 07.01.2009 16:05

«пока вы учитесь на своих ошибках». - Да, если ты умен, со временем ты научишься не делать ситуацию возможной.

Brian R. Bondy 07.01.2009 16:07
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
6
921
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

1) Создайте собственный строковый класс, который наследует / содержит std::string.

2) В этом классе перегрузите operator+=(int val) и сделайте его закрытым.

3) Используйте этот класс для всех ваших строковых операций.

Это приведет к ошибкам флага компилятора всякий раз, когда вы сделаете что-то вроде этого:

MyString str;
str += 27;

трудно обеспечить это для всей команды, а база кода действительно очень большая

Brian R. Bondy 05.01.2009 10:04

Я думаю, что это хорошее решение проблемы OP. Но позже это все равно будет сбивать с толку других разработчиков.

user19302 05.01.2009 10:05

Я думаю это единственный безопасный способ

Brian R. Bondy 05.01.2009 10:45

Я думаю, слишком. Пора написать свой ответ, и вы уже сказали это в 3 предложениях :)

Klaim 05.01.2009 10:53
Ответ принят как подходящий

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

Теперь вы можете "обернуть" std :: basic_string в другой класс использует частное наследование или композицию, а затем использует открытый интерфейс в качестве прокси для части std :: basic_string, но только тех функций, которые вы хотите использовать.

Я рекомендую сначала заменить строковые типы на typedef:

namespace myapp
{
    typedef std::string String;
    typedef std::wstring UTFString;
}

Затем, когда ваше приложение компилируется нормально после замены std :: string и std :: wstring на myapp :: String и myapp :: UTFString (это примеры имен), вы определяете класс-оболочку где-нибудь:

namespace myapp
{
/** std::basic_string with limited and controlled interface.
    */
    template< class _Elem, class _Traits, class _Ax >
    class limited_string 
    {
    public:
        typedef std::basic_string< _Elem , _Traits, _Ax > _String; // this is for easier writing
        typedef limited_string< _Elem, _Traits, _Ax > _MyType; // this is for easier writing

    private:
        _String m_string; // here the real std::basic_string object that will do all the real work!

    public:

        // constructor proxies... (note that those ones are not complete, it should be exactly the same as the original std::basic_string
        // see some STL docs to get the real interface to rewrite)
        limited_string() : m_string {}
        limited_string( const _MyType& l_string ) : m_string( l_string.m_string ) {}
        limited_string( const _Elem* raw_string ) : m_string( raw_string ) {}
        //... etc...

        // operator proxies...
        _MyType& operator= ( const _MyType& l_string ) 
        {
            m_string = l_string.m_string;
        }
        // etc...
        // but we don't want the operator += with int values so we DON'T WRITE IT!

        // other function proxies...
        size_t size() const { return m_string.size(); } // simply forward the call to the real string!
        // etc...you know what i mean...

        // to work automatically with other STL algorithm and functions we add automatic conversion functions:
        operator const _Elem*() const { return m_string.c_str(); } 

        // etc..        


    };
}

... затем вы просто заменяете эти строки:

// instead of those lines...
    //typedef std::string String; 
    //typedef std::wstring UTFString;

    // use those ones
    typedef limited_string< char, std::char_traits<char>, std::allocator<char> >                String; // like std::string typedef
    typedef limited_string< wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> >       UTFString; // like std::wstring typedef 

... и ваш пример выйдет из строя:

error C2676: binary '+=' : 'myapp::UTFString' does not define this operator or a conversion to a type acceptable to the predefined operator
error C2676: binary '+=' : 'myapp::String' does not define this operator or a conversion to a type acceptable to the predefined operator

Вот полный код тестового приложения, который я написал, чтобы доказать это (скомпилирован на vc9):

#include <string>
#include <iostream>

namespace myapp
{

    /** std::basic_string with limited and controlled interface.
    */
    template< class _Elem, class _Traits, class _Ax >
    class limited_string 
    {
    public:
        typedef std::basic_string< _Elem , _Traits, _Ax > _String; // this is for easier writing
        typedef limited_string< _Elem, _Traits, _Ax > _MyType; // this is for easier writing

    private:
        _String m_string; // here the real std::basic_string object that will do all the real work!

    public:

        // constructor proxies... (note that those ones are not complete, it should be exactly the same as the original std::basic_string
        // see some STL docs to get the real interface to rewrite)
        limited_string() : m_string {}
        limited_string( const _MyType& l_string ) : m_string( l_string.m_string ) {}
        limited_string( const _Elem* raw_string ) : m_string( raw_string ) {}
        //... etc...

        // operator proxies...
        _MyType& operator= ( const _MyType& l_string ) 
        {
            m_string = l_string.m_string;
        }
        // etc...
        // but we don't want the operator += with int values so we DON'T WRITE IT!

        // other function proxies...
        size_t size() const { return m_string.size(); } // simply forward the call to the real string!
        // etc...you know what i mean...

        // to work automatically with other STL algorithm and functions we add automatic conversion functions:
        operator const _Elem*() const { return m_string.c_str(); } 

        // etc..        


    };

    // instead of those lines...
    //typedef std::string String; 
    //typedef std::wstring UTFString;

    // use those ones
    typedef limited_string< char, std::char_traits<char>, std::allocator<char> >                String; // like std::string typedef
    typedef limited_string< wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> >       UTFString; // like std::wstring typedef 
}

int main()
{
    using namespace myapp;

    int age = 27;

    UTFString str = UTFString(L"User's age is: ");
    str += age; // compilation error!
    std::wcout << str << std::endl;

    String str2 = String("User's age is: ");
    str2 += age; // compilation error!
    std::cout << str2 << std::endl;

    std::cin.ignore();

    return 0;
}

Я думаю, это решит вашу проблему, но вам придется обернуть все функции.

Нет проблем :) Я хотел убедиться, что это выполнимо, поэтому сначала попробовал. Вид защитного программного мышления: p

Klaim 05.01.2009 11:16

почему бы не получить частный от basic_string и использовать объявление using для повторного объявления функций basic_string в пределах вашего класса? я думаю, что предпочел бы этот подход

Johannes Schaub - litb 05.01.2009 11:23

Да, я предположил, что во втором абзаце я не проверял, но думаю, будет то же самое.

Klaim 05.01.2009 11:28

@litb: вы не должны использовать строковый класс в качестве базового класса для получения других типов. На это указывает отсутствие виртуального деструктора.

Martin York 05.01.2009 11:30

Мартин, да, из-за этого я бы получил личное. вы не можете вызвать удаление на нем извне. но, на самом деле, я только что прочитал статью Херб Саттера, и, честно говоря, я бы больше не использовал частное наследование: D

Johannes Schaub - litb 05.01.2009 11:58

@ Мартин Йорк: И что? basic_string не имеет виртуальных методов. В коде нет полиморфизма, так что все будет хорошо.

Sergey Skoblikov 05.01.2009 11:58

Разве UTFString не сбивает с толку определение типа для std :: wstring?

JesperE 05.01.2009 12:16

Сергей, он говорил о том, что клиенты могут вызывать delete для указателя на std :: string, когда на самом деле он указывает на наш собственный строковый объект. но в любом случае получение частного исправит это.

Johannes Schaub - litb 05.01.2009 12:33

но в любом случае читайте, но в любом случае читайте хорошие статьи gotw.ca/publications/mill06.htm и gotw.ca/gotw/060.htm и gotw.ca/publications/mill07.htm имхо. но простой факт, что у него нет виртуального деструктора, может сигнализировать о том, что он не должен быть получен из, я тоже думаю.

Johannes Schaub - litb 05.01.2009 12:36

@JesperE: Не стесняйтесь менять его на любое более явное имя, которое хотите, это всего лишь пример.

Klaim 05.01.2009 15:13

Если вы хотите играть грубо, вы даже можете для своего проекта переопределить заголовок <string>, чтобы он содержал вашу реализацию limited_string. Экономит много упаковки! Но удовольствие гарантировано на этапе обслуживания :)

xtofl 05.01.2009 23:32

Это не было бы хорошей идеей, поскольку цель здесь не в том, чтобы создать новую строку, а только в том, чтобы ограничить тщательно протестированную и безопасную стандартную строку. :)

Klaim 06.01.2009 18:34

Большинство систем управления версиями позволяют выполнять проверки работоспособности вашего кода во время проверки. Таким образом, вы можете настроить тест, который выполняет проверку и отклоняет проверку в случае сбоя:

Пример:

Тестовый сценарий:

#!/bin/tcsh
# Pass the file to test as the first argument.

echo "#include <string>\
void operator+=(std::string const& , int const&);\
void operator+=(std::string const& , int);"\
| cat - $1 \
| g++ -c -x c++ -  >& /dev/null


echo $status

Этот сценарий имитирует добавление двух указанных выше операторов (без фактического изменения источника). Это приведет к сбою любого использования оператора + со строками и символом, даже если исходный код компилируется.

NB: оператор + = идея украдена из литб. Кто с тех пор удалил свой пример. Но кредит там, где это было необходимо.

Я думаю, что такой ход мысли - самое хорошее решение.

Henk 06.01.2009 00:46

Нет простого способа предотвратить это, но есть простой способ его найти. Напишите небольшую программу, которая использует этот оператор, а затем посмотрите на искаженный символ оператора + =, который вы хотите запретить. Этот символ представляет собой уникальную строку. В рамках автоматизированных тестов используйте DUMPBIN (или аналогичный инструмент для Linux / Mac), чтобы проверить, присутствует ли этот искаженный символ в ваших объектных файлах.

Другие вопросы по теме