Есть ли способ отменить определение + = в строках и 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, чтобы правильно отформатировать строку, но это не то, что я хочу делать. Я хочу полностью запретить этот оператор для этих операндов.
Выстрелить себе в ногу неизбежен. Таким образом, вы должны кодировать, чтобы убедиться, что невозможно выстрелить себе в ногу. В противном случае у вас будет ужасный код.
«Выстрелить себе в ногу неизбежен. Поэтому вы должны кодировать, чтобы убедиться, что невозможно выстрелить себе в ногу». Эти утверждения противоречат друг другу :) И стрельба ногами - самый быстрый путь к тому, чтобы стать лучшим программистом, если вы учитесь на своих ошибках.
Они не противоречат друг другу. Первое заявление было комментарием к ответу litb. Во втором утверждении говорилось, что если у вас есть способ что-то испортить, используя миллионы строк кода, вы в конечном итоге это сделаете. Так кодируйте лучше и сделайте это невозможным, например, сгенерировав ошибку компиляции.
Например: вы можете передать все свои указатели как void *, а затем C-привести их обратно к исходному типу после того, как вы войдете в функцию. Это пример того, что вам не следует делать, потому что в конечном итоге вы выстрелите себе в ногу.
«пока вы учитесь на своих ошибках». - Да, если ты умен, со временем ты научишься не делать ситуацию возможной.





1) Создайте собственный строковый класс, который наследует / содержит std::string.
2) В этом классе перегрузите operator+=(int val) и сделайте его закрытым.
3) Используйте этот класс для всех ваших строковых операций.
Это приведет к ошибкам флага компилятора всякий раз, когда вы сделаете что-то вроде этого:
MyString str;
str += 27;
трудно обеспечить это для всей команды, а база кода действительно очень большая
Я думаю, что это хорошее решение проблемы OP. Но позже это все равно будет сбивать с толку других разработчиков.
Я думаю это единственный безопасный способ
Я думаю, слишком. Пора написать свой ответ, и вы уже сказали это в 3 предложениях :)
Вы не можете отключить определенную функцию класса (здесь 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
почему бы не получить частный от basic_string и использовать объявление using для повторного объявления функций basic_string в пределах вашего класса? я думаю, что предпочел бы этот подход
Да, я предположил, что во втором абзаце я не проверял, но думаю, будет то же самое.
@litb: вы не должны использовать строковый класс в качестве базового класса для получения других типов. На это указывает отсутствие виртуального деструктора.
Мартин, да, из-за этого я бы получил личное. вы не можете вызвать удаление на нем извне. но, на самом деле, я только что прочитал статью Херб Саттера, и, честно говоря, я бы больше не использовал частное наследование: D
@ Мартин Йорк: И что? basic_string не имеет виртуальных методов. В коде нет полиморфизма, так что все будет хорошо.
Разве UTFString не сбивает с толку определение типа для std :: wstring?
Сергей, он говорил о том, что клиенты могут вызывать delete для указателя на std :: string, когда на самом деле он указывает на наш собственный строковый объект. но в любом случае получение частного исправит это.
но в любом случае читайте, но в любом случае читайте хорошие статьи gotw.ca/publications/mill06.htm и gotw.ca/gotw/060.htm и gotw.ca/publications/mill07.htm имхо. но простой факт, что у него нет виртуального деструктора, может сигнализировать о том, что он не должен быть получен из, я тоже думаю.
@JesperE: Не стесняйтесь менять его на любое более явное имя, которое хотите, это всего лишь пример.
Если вы хотите играть грубо, вы даже можете для своего проекта переопределить заголовок <string>, чтобы он содержал вашу реализацию limited_string. Экономит много упаковки! Но удовольствие гарантировано на этапе обслуживания :)
Это не было бы хорошей идеей, поскольку цель здесь не в том, чтобы создать новую строку, а только в том, чтобы ограничить тщательно протестированную и безопасную стандартную строку. :)
Большинство систем управления версиями позволяют выполнять проверки работоспособности вашего кода во время проверки. Таким образом, вы можете настроить тест, который выполняет проверку и отклоняет проверку в случае сбоя:
Пример:
Тестовый сценарий:
#!/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: оператор + = идея украдена из литб. Кто с тех пор удалил свой пример. Но кредит там, где это было необходимо.
Я думаю, что такой ход мысли - самое хорошее решение.
Нет простого способа предотвратить это, но есть простой способ его найти. Напишите небольшую программу, которая использует этот оператор, а затем посмотрите на искаженный символ оператора + =, который вы хотите запретить. Этот символ представляет собой уникальную строку. В рамках автоматизированных тестов используйте DUMPBIN (или аналогичный инструмент для Linux / Mac), чтобы проверить, присутствует ли этот искаженный символ в ваших объектных файлах.
Я бы сказал, что это не проблема. всегда можно найти способ прострелить себе ногу. Лучше научите коллег правильно программировать на C++ :)