Здравствуйте, adontz, Вы писали:
A>Здравствуйте, Шахтер, Вы писали:
A>>>Делись! Ш>>Знаешь, есть такое слово -- копирайт.
A>ОК. Функциональные требования можешь представить? Типа чего бы ты хотел от строкового класса, если бы реализовывал не сам.
Здравствуйте, adontz, Вы писали:
A>Здравствуйте, Шахтер, Вы писали:
CS>>>Это ты про std::string ? Ш>>Ну почему. Про любую вменяемую реализацию строк.
A>странные ты вещи говоришь. По твоим словам выходит так.
A>
A>1 std::string a;
A>2 std::string b;
A>3 a = "aaaa";
A>4 b = a;
A>5 a = "bbbb";
A>
A>Либо после выполнения пятой строки значение b так же равно "bbbb", либо при выполнении пятой строки создаётся копия строки a (copy on write), а для этого строка a должна умето получать список всех своих копий. A>И то и другое мало похоже на правду
Чего-то я (наверное по недомыслию) не нахожу неоднозначностей в твоем примере... Как вариант моей трактовки:
1 std::string a;
2 std::string b;
3 a = "aaaa"; // reference count to "aaaa" = 1 (self)
4 b = a; // reference count to "aaaa" = 2 (a and b). Reference count to "aaaa" in b instance = 1;
5 a = "bbbb"; // a now owned another string ("bbbb").
// Decrement reference count to "aaaa" (if 0 - delete "aaaa"),
// then allocate new buffer for string, copy string to buffer and set
// reference count to 1 (exclusive ownership).
// In this sample, b had continue ownership to string "aaaa" with reference count = 1.
Сорри фор инглиш — пиво было хорошее, и, как следствие закона сохранения энергии — инглиш получился фиговым
AD>>Не все реализации std::string основаны на подсчёте ссылок. Для обычных std::string операция копировния по скорости аналогична std::vector<char>. Ш>Это каких, обычных? Если реализация string клонирует строку при копировании, это значит -- кривая реализация.
Не спорю, свои приемущества в подсчёте ссылок есть. Особенно, если программист злоупотребляет такими констркуциями как:
void some_func(std::string arg1, std::string arg2) // так пишет "криворукий" программист :))
{
Или при сортировке строк. Правда, если переопределить функцию swap:
то это приемущество отпадает. Никаких других приемуществ я не знаю. Если я копирую одну строчку в другую, то то с вероятностью 95% я это делаю для её модификации, и подсчёт сылок мне нафиг не нужен.
AD>>Кроме того, для std::vector<char> мы гарантированно имеем неразрывную последовательность данных в памяти (иногда это бывает очень важно).
Ш>При работе со строками, обычно это неважно.
Когда как.
Здравствуйте, Шахтер, Вы писали:
ME>>Большинство "вменяемых" стандартных строк уже не используют reference counting (Mad COW Disease).
Ш>Опять "эксперты" постарались? А тебе не кажется что это не Mad COW Disease, а в Mad Expert Disease? Рассуждения про многопоточность -- типичное горе от "ума". От стандартных строк не требуется thread-safety.
Проблема в том, что в реальной жизни многопоточность присутствует. И при использовании в реализации подсчета ссылок синхронизацию просто приходится помещать в класс строки, т.к. в противном случае прикладному коду надо будет осуществлять синхронизацию буквально при каждой операции с "подозрительными" строками — и подозрительными будут, фактически, все строки, переданные "извне", т.к. синхронизация нужна будет не только при работе непосредственно с разделяемыми ресурсами, но и с любой строкой, которая могла быть скопирована из разделяемой. Например, если есть некоторая "универсальная", библиотечная, функция:
то при использовании в реализации std::string подсчета ссылок и отсутствии синхронизации, эту функцию нужно будет как-то переписать, включив в нее синхронизацию доступа к строке, т.к. потенциально строка s1 вполне могла быть когда-то скопирована из строки, использующейся параллельно другим потоком. Но т.к. эта функция библиотечная, сделать это будет очень затруднительно; даже подходящий мьютекс выбрать весьма сложно — ведь о логике работы приложения библиотека понятия не имеет...
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Здравствуйте, Шахтер, Вы писали:
ME>>>Большинство "вменяемых" стандартных строк уже не используют reference counting (Mad COW Disease).
Ш>>Опять "эксперты" постарались? А тебе не кажется что это не Mad COW Disease, а в Mad Expert Disease? Рассуждения про многопоточность -- типичное горе от "ума". От стандартных строк не требуется thread-safety.
ПК>Проблема в том, что в реальной жизни многопоточность присутствует. И при использовании в реализации подсчета ссылок синхронизацию просто приходится помещать в класс строки, ...
1) Безусловно, присутствует. Я, однако, считаю, что не следует нагружать базовые механизмы многопоточной синхронизацией. Вообще, проектирование многопоточных систем довольно сильно отличается от проектирования однопоточных, механически использовать решения, хорошо работающие в однопоточном случае, в многопоточных приложениях нельзя. Заниматься передачей строк бездумно при пересечении границ потоков не стоит.
2) По-хорошему, должен быть выбор. Не должно быть одного string класса на все случаи жизни. Разные задачи требуют разных реализаций.
Павел Кузнецов wrote:
> Проблема в том, что в реальной жизни многопоточность присутствует. И при использовании в реализации подсчета ссылок синхронизацию просто приходится помещать в класс строки, т.к. в противном случае прикладному коду надо будет осуществлять синхронизацию буквально при каждой операции с "подозрительными" строками — и подозрительными будут, фактически, все строки, переданные "извне", т.к. синхронизация нужна будет не только при работе непосредственно с разделяемыми ресурсами, но и с любой строкой, которая могла быть скопирована из разделяемой.
Многопоточные приложения требуют более аккуратного проектирования. Результатом этого проектирования должен быть такой дизайн, когда между потоками очень узкие интерфейсы, в которых необходима синхронизация. В этом случае отпадает необходимость синхронизировать все и вся.
Здравствуйте, ArtDenis, Вы писали:
AD>Здравствуйте, Шахтер, Вы писали:
Ш>> Дождался. Большого и тухлого. Ш>>
Ш>> class CTask
Ш>> {
Ш>> int id;
Ш>> std::string message;
Ш>> public:
Ш>> ...
Ш>>
Ш>> Опрация копирования для векторов неэффективна. Надо использовать std::string.
AD>Не все реализации std::string основаны на подсчёте ссылок. Для обычных std::string операция копировния по скорости аналогична std::vector<char>.
Это каких, обычных? Если реализация string клонирует строку при копировании, это значит -- кривая реализация.
AD>Кроме того, для std::vector<char> мы гарантированно имеем неразрывную последовательность данных в памяти (иногда это бывает очень важно).
Здравствуйте, adontz, Вы писали:
A>Здравствуйте, Шахтер, Вы писали:
CS>>>Это ты про std::string ? Ш>>Ну почему. Про любую вменяемую реализацию строк.
A>странные ты вещи говоришь. По твоим словам выходит так.
A>
A>1 std::string a;
A>2 std::string b;
A>3 a = "aaaa";
A>4 b = a;
A>5 a = "bbbb";
A>
A>Либо после выполнения пятой строки значение b так же равно "bbbb", либо при выполнении пятой строки создаётся копия строки a (copy on write), а для этого строка a должна умето получать список всех своих копий. A>И то и другое мало похоже на правду
Не буду читать сейчас лекции про строки -- у меня сегодня первый день отпуска. Просто замечание. Хорошая реализация строкового класса должна иметь дешёвую реализацию операции копирования, которая должна быть не только O(1) по сложности, а я бы сказал даже O(0), но и не должна порождать исключений. Представь себе оператор сложения строк. В нем есть возврат строки. Если ты копируешь строки клонированием, то ты автоматически удваиваешь сложность операции сложения. В ряде случаев компилятор может тут соптимизировать, но не во всех (зависит от контекста, от качества оптимизатора, от опций, от фаз Луны, наконец). Для строк операция копировани -- естественная и часто используемая. Поэтому должна быть дешёвой. А иначе такой строковый класс нафик не нужен.
[]
F>Чего-то я (наверное по недомыслию) не нахожу неоднозначностей в твоем примере... Как вариант моей трактовки:
F>
F>1 std::string a;
F>2 std::string b;
F>3 a = "aaaa"; // reference count to "aaaa" = 1 (self)
F>4 b = a; // reference count to "aaaa" = 2 (a and b). Reference count to "aaaa" in b instance = 1;
F>5 a = "bbbb"; // a now owned another string ("bbbb").
F> // Decrement reference count to "aaaa" (if 0 - delete "aaaa"),
F> // then allocate new buffer for string, copy string to buffer and set
F> // reference count to 1 (exclusive ownership).
F> // In this sample, b had continue ownership to string "aaaa" with reference count = 1.
F>
С copy on write не все так хорошо как кажется. Например:
string a = "aaaa";
string b = a;
cout << a[1] << '\n'; // (*)
Оператор [] для неконстантной строки должен вернуть std::allocator<char>::reference, т.е. char&. Поскольку нельзя допускать, чтобы shared buffer был изменен через эту ссылку, то в строчке (*) должна быть создана копия исходной строки.
Вот такой уродский интерфейс у std::string
F>Сорри фор инглиш — пиво было хорошее, и, как следствие закона сохранения энергии — инглиш получился фиговым
Здравствуйте, folk, Вы писали:
F>Здравствуйте, Шахтер, Вы писали:
Ш>>Да, но без COW копии будут создаваться всегда. Т.е. экономия всё равно есть, пусть и не 100%.
F>С COW усложнится релизация.
Это плата за устранение клонирования.
Ш>>А вообще -- не пользуйтесь stl!
F>Как так? Пользоваться самодельными контейнерами/алгоритмами? Ты зовешь нас в каменный век!
Я зову пользоваться теми средствами, которые отвечают существу задачи. Причем здесь каменный век? Если stl не предоставляет подходящего средства -- значит нужно использовать другое. И не обязательно самописное. Хотя умение делать контейнеры и писать алгоритмы -- по-моему, это часть профессиии.
Ш>>Мне кажется, что немутирующие строки с подсчетом ссылок для приложений полезнее.
F>Это да. В дополнение к контейнеру а-ля StringBuilder.
О том и речь. Классический string из stl не вполне удачно спроектирован. Некоторые напористые люди успели его впаять в стандарт. Как результат -- пользоваться им нельзя. Абзац.
Здравствуйте, Шахтер, Вы писали:
Ш>Смотри первоначальную задачу. И подумай о преимуществах и криворукости.
Да, в плане первоначальной задачи ты прав. Осталось только выяснить, в каких реализациях STL класс std::string основан на подсчёте ссылок, чтобы использовать именно эту реализацию.
PS: ничего против подсчёта ссылок в строчках не имею, уже пяток лет в Билдере юзаю AnsiString, который на нём и основан. И ничего, доволен
Здравствуйте, Павел Кузнецов, Вы писали:
ME>>>Большинство "вменяемых" стандартных строк уже не используют reference counting (Mad COW Disease).
ПК>Проблема в том, что в реальной жизни многопоточность присутствует. И при использовании в реализации подсчета ссылок ...
... ПК>то при использовании в реализации std::string подсчета ссылок и отсутствии синхронизации, эту функцию нужно будет как-то переписать
Лично мне кажется что многопоточность и подсчет ссылок в строках это несколько ортогональные вещи.
Голая конструкция типа
static std::string mystring;
принципиально потоко-опасна какой бы механизм не был зашит в std::string.
Я к тому что поддержка многопоточности в библиотеках общего назанчения типа STL
должна предоставлять имплементацию механизмов поддержки "периметра" и инетерфейса потоков, маршаллинг/сериализация, interthread messaging
и т.д.
Т.е. std::string должна быть имплементирована максимально эффективно для работы "внутри" потока, модуля, библиотеки.
А вопросы многопоточности решаться на другом уровне и другими методами.
Самая лучшая поддержка многопоточности — это как ни странно имплементация более менее удобоваримого механизма type_info со товарищи.
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Шахтер:
>> в конце концов, можно сделать две реализации -- одна быстрая, а вторая -- специально для тупо написанного многопоточного кода. Может я слишком много хочу от программистов?
ПК>Скорее, от производителей компиляторов: предоставлять реализацию std::string, неустойчивую к погрешностям проектирования пользовательских приложений, но несколько более эффективную в некоторых частных случаях, для них совершенно невыгодно.
Ну на счет эффективности никогда нельзя быть субъективным
я например искренне верил что копирование миллиона строк должно
быть медленнее чем работа со счетчиком ссылок .
После небольшого эксперимента я был просто в шоке.
Теперь я понял в чем была аргументация авторов клонирующей реализации:
для недлинных строк разницу между копированием и чистым cow
обнаружить _НЕВОЗМОЖНО_. И этому есть простое объяснение.
Короткое резюме — менять строки изменяя storage policy это хорошо
необходимость в этом возникает только если все совсем плохо
те либо не хватает памяти либо нужно выжать _все_ что есть.
Здравствуйте, Шахтер, Вы писали:
Ш>Я видел реализации с подсчетом ссылок. Давно правда это было.
ИМХО, счастье, что давно. Целиком согласен с аргументами, что в большинстве реальных приложений reference counting для строк ничего кроме дополнительных тормозов, либо огромого потенциала для глюков в MT не дает.
И reference counting не уменьшает сложность копирования до O(1). Он просто переносит это копирование на этап записи.
A>>Максимум это то, что копирование скорее всего сделано не в цикле по элементам, а с помошью memmove. Но в любом случае никакого подсчёта ссылок там нет. Так что ты жестоко ошибался.
Ш>Повторю ещё раз. Я НЕ пользуюсь stl. Одна из причин, что не никаких гарантий, как реализован там тот или иной класс.
Интересно, насколько это реально оправдано в твоем случае?
Здравствуйте, Шахтер, Вы писали:
Ш>Я извиняюсь, но никаких гарантий касающихся многопоточности стандарт относительно класса string не даёт, так что разработчики библиоткеки вольны делать любую реализацию. А руки надо отрывать людям, которые его бездумно использовали.
Есть такая вещь, как нормальное ожидание поведения объекта. От operator= программист обычно ожидает, что будет создана копия объекта, поскольку именно так ведут себя все встроенные типы. Ты предлагаешь поменять нормальную семантику оператора = и заставить программиста думать на каждом шаге о синхронизации? Как тебе уже сказали, ни к чему кроме трудноуловимых и дорогостоящих глюков это не приведет.
... << RSDN@Home 1.1.4 beta 1 >>
Re[12]: Подсчет ссылок в реализации std:
От:
Аноним
Дата:
30.07.04 11:13
Оценка:
Здравствуйте, e-Xecutor, Вы писали:
EX>Здравствуйте, Кодт, Вы писали:
Народ! поделитесь плз, какой редиска нашептал вам на ушко, что киее либо stl контейнеры можно передавать между потоками???
откуда вообще беруться такие бредовые идеи? нравится работать с std::string — замечательно...; нужно передать строку в другой поток — передавай указаль а уже в получающем потоке кидай указатель в string! и даже при таком вареанте нужна синхронизация: ты можеш удалить указатель до того, как он попадет в принимающий поток и скопируется его значение, так что же вообще говорить о контейнерах??? и никакие блокировки, вставленные внутрь контейнера вам никогда не помогут в многопоточной среде, про это надо забыть как про то что можно писать в штанишки!
Здравствуйте, Аноним, Вы писали:
А>Народ! поделитесь плз, какой редиска нашептал вам на ушко, что киее либо stl контейнеры можно передавать между потоками??? А>откуда вообще беруться такие бредовые идеи? нравится работать с std::string — замечательно...; нужно передать строку в другой поток — передавай указаль а уже в получающем потоке кидай указатель в string! и даже при таком вареанте нужна синхронизация: ты можеш удалить указатель до того, как он попадет в принимающий поток и скопируется его значение, так что же вообще говорить о контейнерах??? и никакие блокировки, вставленные внутрь контейнера вам никогда не помогут в многопоточной среде, про это надо забыть как про то что можно писать в штанишки!
(Сказка произносится тихим, сиплым, страшным голосом — в надежде, что собеседник таки написает в штанишки).
Ты или на молоке обжёгся и дуешь на воду, или где-то что-то глубоко недопонял.
Не говоря уже о фразе "можешь удалить указатель". Это как? delete new LPVOID (some_pointer_value) ?
Здравствуйте, ArtDenis, Вы писали:
AD>Здравствуйте, Flamer, Вы писали:
F>> Есть некий класс, в котором отсутствует конструктор без параметров. F>> Интересные места выделены жирным: [ccode]
AD>У... Как всё у нас тут запущено...
AD>Сейчас кое-кто забросает меня тухлыми яйцами, но если сделать вот так:
AD>
AD>то можно избавиться от конструктора копирования, оператора присваивания, оператора new и геммороя с освобождением памяти. И код будет в четыре раза короче.
AD>ЗЫ: сижу в резиновом плаще и жду
Дождался. Большого и тухлого.
class CTask
{
int id;
std::string message;
public:
...
Опрация копирования для векторов неэффективна. Надо использовать std::string.
Здравствуйте, MaximE, Вы писали:
ME>Шахтер wrote:
>> Опрация копирования для векторов неэффективна. Надо использовать std::string.
ME>Копирование у строк эффективние? Хотелось бы узнать подробности.
ME>-- ME>Maxim Yegorushkin
При копировании вектора создаётся копия данных. При копировании строк накручивается счётчик ссылок.
Здравствуйте, c-smile, Вы писали:
CS>Здравствуйте, Шахтер, Вы писали:
Ш>>При копировании вектора создаётся копия данных. При копировании строк накручивается счётчик ссылок.
CS>Это ты про std::string ?
Здравствуйте, Шахтер, Вы писали:
CS>>Это ты про std::string ? Ш>Ну почему. Про любую вменяемую реализацию строк.
странные ты вещи говоришь. По твоим словам выходит так.
1 std::string a;
2 std::string b;
3 a = "aaaa";
4 b = a;
5 a = "bbbb";
Либо после выполнения пятой строки значение b так же равно "bbbb", либо при выполнении пятой строки создаётся копия строки a (copy on write), а для этого строка a должна умето получать список всех своих копий.
И то и другое мало похоже на правду
Здравствуйте, Шахтер, Вы писали:
Ш>Не буду читать сейчас лекции про строки -- у меня сегодня первый день отпуска. Просто замечание.
Поздравляю. с отпуском.
Ш>Хорошая реализация строкового класса должна иметь дешёвую реализацию операции копирования, которая должна быть не только O(1) по сложности, а я бы сказал даже O(0), но и не должна порождать исключений. Представь себе оператор сложения строк. В нем есть возврат строки. Если ты копируешь строки клонированием, то ты автоматически удваиваешь сложность операции сложения. В ряде случаев компилятор может тут соптимизировать, но не во всех (зависит от контекста, от качества оптимизатора, от опций, от фаз Луны, наконец). Для строк операция копировани -- естественная и часто используемая. Поэтому должна быть дешёвой. А иначе такой строковый класс нафик не нужен.
Ээээ. Ты тёплое с мягким не путай.
Первое.
Если нет RVO и проч. оптимизаций то запись
c = a + b;
должна работать так.
1) создаёться временный объект в который записывается строка a+b
2) вызывается c.operator=(временный объект).
Ты хочешь оптимизировать за счёт пункта 2. Решение правильное, но только это не копирование, а перемещение. (не COPY, а MOVE). Для реализации этой идеи никакого подсчёта ссылок не нужно. Подробнее тут http://www.cuj.com/documents/s=8246/cujcexp2102alexandr/alexandr.htm
Второе.
Предлагая использовать std::string вместо std::vector<char> ты указывал, что копирование в std::string сделано намного эффективнее. На самом деле это не так. Во всяком случае во всех известных мне реализациях.
Максимум это то, что копирование скорее всего сделано не в цикле по элементам, а с помошью memmove. Но в любом случае никакого подсчёта ссылок там нет. Так что ты жестоко ошибался.
Здравствуйте, Flamer, Вы писали:
F>Чего-то я (наверное по недомыслию) не нахожу неоднозначностей в твоем примере... Как вариант моей трактовки:
А кто сказал, что етсь неоднозначности?
F>
F>1 std::string a;
F>2 std::string b;
F>3 a = "aaaa"; // reference count to "aaaa" = 1 (self)
F>4 b = a; // reference count to "aaaa" = 2 (a and b). Reference count to "aaaa" in b instance = 1;
F>5 a = "bbbb"; // a now owned another string ("bbbb").
F> // Decrement reference count to "aaaa" (if 0 - delete "aaaa"),
F> // then allocate new buffer for string, copy string to buffer and set
F> // reference count to 1 (exclusive ownership).
F> // In this sample, b had continue ownership to string "aaaa" with reference count = 1.
F>
Это и есть по сути copy in write. При попытке изменить исходную строку она как бы отделяется от своей копии и живёт отдельной жизнью.
Ничего невозможного в этом нет, кроме того, что это невозможно увидеть в STL Ведь мы обсуждаем не абстракный идеальный класс строки, а std::string и его преимущества перед std::vectro<char> в плане копирования.
Здравствуйте, adontz, Вы писали:
A>Если нет RVO и проч. оптимизаций то запись A>c = a + b; A>должна работать так.
Кому должна? Ты указывал на известные тебе реализации, ну так как обстоит дело в них?
A>1) создаёться временный объект в который записывается строка a+b A>2) вызывается c.operator=(временный объект). A>Ты хочешь оптимизировать за счёт пункта 2. Решение правильное, но только это не копирование, а перемещение. (не COPY, а MOVE). Для реализации этой идеи никакого подсчёта ссылок не нужно. Подробнее тут http://www.cuj.com/documents/s=8246/cujcexp2102alexandr/alexandr.htm
MOJO -- трюкачество.
Всё это напоминает один анекдот. Русские сделали крутой-прекрутой вездеход. Привезли его в Японию на выставку. Японцы на него посмотрели и сказали -- даа, класный вездеход. И чего эти русские только не придумают, лишь бы дороги не ремонтировать.
A>Второе.
A>Предлагая использовать std::string вместо std::vector<char> ты указывал, что копирование в std::string сделано намного эффективнее.
Нет, я писал, что в приличной реализации должно быть более эффективной. Но даже если это не так, то ты ничего не теряешь по сравнению с вектором. И есть шанс выиграть. Так что смысл использовать для хранение строк string вместо vector всё равно есть.
A>На самом деле это не так. Во всяком случае во всех известных мне реализациях.
Я видел реализации с подсчетом ссылок. Давно правда это было.
A>Максимум это то, что копирование скорее всего сделано не в цикле по элементам, а с помошью memmove. Но в любом случае никакого подсчёта ссылок там нет. Так что ты жестоко ошибался.
Повторю ещё раз. Я НЕ пользуюсь stl. Одна из причин, что не никаких гарантий, как реализован там тот или иной класс.
Здравствуйте, folk, Вы писали:
F>Здравствуйте, Flamer, Вы писали:
F>[]
F>>Чего-то я (наверное по недомыслию) не нахожу неоднозначностей в твоем примере... Как вариант моей трактовки:
F>>
F>>1 std::string a;
F>>2 std::string b;
F>>3 a = "aaaa"; // reference count to "aaaa" = 1 (self)
F>>4 b = a; // reference count to "aaaa" = 2 (a and b). Reference count to "aaaa" in b instance = 1;
F>>5 a = "bbbb"; // a now owned another string ("bbbb").
F>> // Decrement reference count to "aaaa" (if 0 - delete "aaaa"),
F>> // then allocate new buffer for string, copy string to buffer and set
F>> // reference count to 1 (exclusive ownership).
F>> // In this sample, b had continue ownership to string "aaaa" with reference count = 1.
F>>
F>С copy on write не все так хорошо как кажется. Например: F>
F>string a = "aaaa";
F>string b = a;
F>cout << a[1] << '\n'; // (*)
F>
F>Оператор [] для неконстантной строки должен вернуть std::allocator<char>::reference, т.е. char&. Поскольку нельзя допускать, чтобы shared buffer был изменен через эту ссылку, то в строчке (*) должна быть создана копия исходной строки.
Да, но без COW копии будут создаваться всегда. Т.е. экономия всё равно есть, пусть и не 100%. А вообще -- не пользуйтесь stl!
Мне кажется, что немутирующие строки с подсчетом ссылок для приложений полезнее.
Здравствуйте, adontz, Вы писали:
F>>Вот такой уродский интерфейс у std::string
A>Что-то я не понял. Я специально поглядел свой STL. Этот ужас что, правда где-то есть?
Я о таких реализациях ничего не знаю (честно говоря работал только с Dinkumware и STLport). Говорил чисто теоретически, ведь если делать string с COW, то придется как-нибудь разруливать подобные проблемы.
Еще будет чревато передавать shared-строку в другой поток. Не помню кто, кажется Саттер или Мейерс рассматривали COW применительно к строкам.
Здравствуйте, Шахтер, Вы писали:
Ш>Да, но без COW копии будут создаваться всегда. Т.е. экономия всё равно есть, пусть и не 100%.
С COW усложнится релизация.
Ш>А вообще -- не пользуйтесь stl!
Как так? Пользоваться самодельными контейнерами/алгоритмами? Ты зовешь нас в каменный век!
Ш>Мне кажется, что немутирующие строки с подсчетом ссылок для приложений полезнее.
Это да. В дополнение к контейнеру а-ля StringBuilder.
...
So deeply rooted is the idea that copy-on-write reference counting is mandatory for strings that many developers are shocked — and sometimes go into denial — when they discover that the return on investment in this technique is often negligible and sometimes negative. The long-standing belief in this old practice is, however, younger than faith in another more fundamental software engineering principle: separation of concerns. And hey, do we have concerns.
...
...
Without atomic integer operations, copy-on-write typically incurs a significant performance penalty. Even with atomic integer operations, COW can make common String operations take nearly 50% longer -- even in single-threaded programs.
In general, copy-on-write is often a bad idea in multithread-ready code. In short, the reason is that the calling code can no longer know whether two distinct String objects actually share the same representation under the covers, and so String must incur overhead to do enough serialization that calling code can take its normal share of responsibility for thread safety. Depending on the availability of more-efficient options like atomic integer operations, the impact on performance ranges from "moderate" to "profound."
...
-- Maxim Yegorushkin
Posted via RSDN NNTP Server 1.9 beta
Re[11]: Подсчет ссылок в реализации std
От:
Аноним
Дата:
23.07.04 07:37
Оценка:
Здравствуйте, Шахтер, Вы писали:
Ш>Да, но без COW копии будут создаваться всегда. Т.е. экономия всё равно есть, пусть и не 100%. А вообще -- не пользуйтесь stl! Ш>Мне кажется, что немутирующие строки с подсчетом ссылок для приложений полезнее.
Здравствуйте, MaximE, Вы писали:
ME>Шахтер wrote:
>> Здравствуйте, c-smile, Вы писали: >> >> CS>Здравствуйте, Шахтер, Вы писали: >> >> Ш>>При копировании вектора создаётся копия данных. При копировании строк накручивается счётчик ссылок. >> >> CS>Это ты про std::string ? >> >> Ну почему. Про любую вменяемую реализацию строк.
ME>Большинство "вменяемых" стандартных строк уже не используют reference counting (Mad COW Disease).
Опять "эксперты" постарались? А тебе не кажется что это не Mad COW Disease, а в Mad Expert Disease? Рассуждения про многопоточность -- типичное горе от "ума". От стандартных строк не требуется thread-safety. Да и самого понятия "поток" в стандарте нет. Вообще странно, никто не требует от целочисленных типов, например, thread-safety. Так почему некотрые "эксперты" стараются засунуть мьютексы в реализацию строк?
А вообще пора вернуться к теме. В оригинальном примере применение строк, основанных на использовании счётчика ссылок, -- это то что доктор прописал. Причем, как я понял, вполне достаточно немутирующих строк -- соответсвенно, никаких проблем с COW нет.
Шахтер:
> 2) По-хорошему, должен быть выбор. Не должно быть одного string класса на все случаи жизни.
Конечно, всегда можно написать специальные классы базовых примитивов, в т.ч. и строк, оптимизированных под конкретное приложение. Разве что, в большинстве случаев прикладного программирования это совершенно неэффективно с точки зрения соотношения производительность/стоимость разработки. Don't fix what isn't broken. В том смысле, что в большинстве приложений копирование строк и близко не появляется в первой сотне функций, выделенных профайлером. А если появляется, то обычно более эффективным способом является не переписывание класса строки, а перепроектирование приложения.
> 3) Вместо <...> можно сделать >
Теоретически — да. На практике же, забыв в одном месте написать DetachedCopy(str,str_) вместо str=str_, наведенные, плохо воспроизводящиеся, ошибки можно отлавливать еще очень долго.
В общем, для реализаций std::string, учитывая имеющийся "богатый" интерфейс последней, от подсчета ссылок отказались если не все, то большинство разработчиков стандартных библиотек.
Posted via RSDN NNTP Server 1.9 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Шахтер:
>> 2) По-хорошему, должен быть выбор. Не должно быть одного string класса на все случаи жизни.
ПК>Конечно, всегда можно написать специальные классы базовых примитивов, в т.ч. и строк, оптимизированных под конкретное приложение.
Под конкретное поведение. Поведений гораздо меньше, чем приложений. Ну, собственно, Александреску там что-то такое предлагал -- гибко настраиваемое.
ПК>Разве что, в большинстве случаев прикладного программирования это совершенно неэффективно с точки зрения соотношения производительность/стоимость разработки.
Вы неправильно считаете деньги. Это делается один раз -- хорошо. Или много раз плохо. В рамках написания очередной "экспертной" реализации stl.
ПК>Don't fix what isn't broken. В том смысле, что в большинстве приложений копирование строк и близко не появляется в первой сотне функций, выделенных профайлером.
А вот я, например, не делаю это большинство. Мне регулярно приходится заниматься задачами, где производительность принципиальна. А что касается большинства, то большинство программирует вообще на Бейсике. Для этого большинства сейчас есть .Net. Вот в этом детском бассейне оно и беззаботно развлекается. Прогресс он не стоит на месте. Для типовых задач есть куча RADостей. А над остальным приходиться думать.
Дело в конце концов не в производительности, а в принципе. Программирование -- это управление ресурсами. И начинается оно с мелочей. Если человек не может грамотно подобрать строковый класс, то извините, куда ему более серьёзные задачи решать -- не справиться.
ПК>А если появляется, то обычно более эффективным способом является не переписывание класса строки, а перепроектирование приложения.
>> 3) Вместо <...> можно сделать >>
ПК>Теоретически — да. На практике же, забыв в одном месте написать DetachedCopy(str,str_) вместо str=str_, наведенные, плохо воспроизводящиеся, ошибки можно отлавливать еще очень долго.
Интересно, и сколько таких мест будет? Имея реализацию с подсчетом ссылок, можно без труда её завернуть во враппер и сделать реализацию с клонированием. А вот назад -- фигушки.
ПК>В общем, для реализаций std::string, учитывая имеющийся "богатый" интерфейс последней, от подсчета ссылок отказались если не все, то большинство разработчиков стандартных библиотек.
Всё что вы говорите, на самом деле, лишь подтверждает, что stl плохо спроектирована.
Ш>Это каких, обычных? Если реализация string клонирует строку при копировании, это значит -- кривая реализация.
Если реализация std::string сделана с подсчётом ссылок,
то отрывать руки надо за такую реализацию.
Жил был очень многопоточный проект.
Около 50-ти потоков. Компилялся ГэЦэЦой.
Всё было более или менее хорошо, ровно до того
момента когда в сервер не воткнули второй процессор.
В общем... Не знаю как сейчас, но тогда
нам не удалось заменить в gcc(3.0) stl.
Перешли на сановский компилятор.
Те же яйца, вид в профиль — там был рогвэйв
с по умолчанию включенным счётчиком ссылок.
Теоретически отключить можно, но практически нельзя.
Зато можно STLPort подключить. Что было сделано и в общем и целом помогло...
Вариант с неиспользованием STL рассматривался... Но как "на крайний случай".
P.S. я ничего не имею непосредственно против идеологии подсчёта ссылок.
У нас есть куча классов тем или иным образом использующих подсчёт ссылок.
Проблема, по сути, была в конкретных реализациях, и дизайне класса
std::string, не предусматривающем подсчёт ссылок.
Вот и не надо было его туда прикручивать...
Шахтер wrote:
> ME>Большинство "вменяемых" стандартных строк уже не используют reference counting (Mad COW Disease). > > Опять "эксперты" постарались? А тебе не кажется что это не Mad COW Disease, а в Mad Expert Disease? Рассуждения про многопоточность -- типичное горе от "ума". От стандартных строк не требуется thread-safety. Да и самого понятия "поток" в стандарте нет. Вообще странно, никто не требует от целочисленных типов, например, thread-safety. Так почему некотрые "эксперты" стараются засунуть мьютексы в реализацию строк?
Согласен в том, что строка не должна быть потокобезопасной.
> А вообще пора вернуться к теме. В оригинальном примере применение строк, основанных на использовании счётчика ссылок, -- это то что доктор прописал. Причем, как я понял, вполне достаточно немутирующих строк -- соответсвенно, никаких проблем с COW нет.
Как раз про это и пишет Kevlin Henney в вышеупомянутой статье. Стандартный класс строк спроектирован неряшливо — перегруженный интерефейс, слишком много функционала, чтобы сделать этот класс эффективным. От того и проблема, что когда стандартную строку хотят оптимизировать при помощи COW, приходится запихивать в нее синхронизацию, так как многопоточный код пишется в расчете на то, что строка "тупая" — будет копироваться, когда ее передают по значению.
[]
> Всё что вы говорите, на самом деле, лишь подтверждает, что stl плохо спроектирована.
std::string никогда не входил в stl, в чем ты можешь найти подтверждение посмотрев как наскорую "прикручены" к ней итераторы. stl и стандартная библиотека не синонимы.
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>...Don't fix what isn't broken...
А если дизайн broken изначально? Или не подходит к задачам реального мира в 70% случаев?
Пример: XML/HTML — аттрибуты и их значения повторяются примерно на 70-90% в одном документе.
Слепая имплементация в стиле
element {
std::vector< std::pair<std::string,std::string>> attributes;
}
Ведет к неоправданному перерасходу ресурсов и чудовищной неэффективности.
Попытка имплементации "как должно" (интернирование строк например) средствами стандартной библиотеки
приводит к неуклюжим, неэффективным и эмпирически недоказуемым конструкциям.
Здравствуйте, c-smile, Вы писали:
CS>Лично мне кажется что многопоточность и подсчет ссылок в строках это несколько ортогональные вещи.
CS>Голая конструкция типа CS>
CS>static std::string mystring;
CS>
CS>принципиально потоко-опасна какой бы механизм не был зашит в std::string.
Пардон, может я не очень хорошо помню имплементацию COW, синхронизация нужна для безопасного изменения каунтера, что с успехом достаточно дешево можно проделать на на многих платформах. А содержимое буфера синхронизировать нет нужды, каждая операция модификации будет строить буфер по новой.
Will I live tomorrow? Well I just can't say
But I know for sure — I don't live today.
Jimi Hendrix.
Здравствуйте, MaximE, Вы писали:
ME>Шахтер wrote:
ME>[]
>> Всё что вы говорите, на самом деле, лишь подтверждает, что stl плохо спроектирована.
ME>std::string никогда не входил в stl, в чем ты можешь найти подтверждение посмотрев как наскорую "прикручены" к ней итераторы. stl и стандартная библиотека не синонимы.
ME>-- ME>Maxim Yegorushkin
Прошу прощения у stl. Делаем поправку -- стандартная библиотека.
Здравствуйте, _nn_, Вы писали:
__>Здравствуйте, Шахтер, Вы писали:
__>Может стоит открыть проект на RSDN и написать нормальный класс строки, aka RSDN::string
Да здесь не нужен проект. Скорее -- ФАК, как писать строки, как впрочем и другие контейнеры. Я этого добра написал столько, что теперь всё что мне нужно пишу чуть ли не с закрытими глазами: берётся код из архива, скрещивается, мутируется, дорабатывем напильником -- и готово.
Здравствуйте, e-Xecutor, Вы писали:
EX>Здравствуйте, Шахтер, Вы писали:
Ш>>Это каких, обычных? Если реализация string клонирует строку при копировании, это значит -- кривая реализация.
EX>Если реализация std::string сделана с подсчётом ссылок, EX>то отрывать руки надо за такую реализацию.
EX>Жил был очень многопоточный проект.
Я извиняюсь, но никаких гарантий касающихся многопоточности стандарт относительно класса string не даёт, так что разработчики библиоткеки вольны делать любую реализацию. А руки надо отрывать людям, которые его бездумно использовали.
Здравствуйте, MaximE, Вы писали:
>> А вообще пора вернуться к теме. В оригинальном примере применение строк, основанных на использовании счётчика ссылок, -- это то что доктор прописал. Причем, как я понял, вполне достаточно немутирующих строк -- соответсвенно, никаких проблем с COW нет.
ME>Как раз про это и пишет Kevlin Henney в вышеупомянутой статье. Стандартный класс строк спроектирован неряшливо — перегруженный интерефейс, слишком много функционала, чтобы сделать этот класс эффективным. От того и проблема, что когда стандартную строку хотят оптимизировать при помощи COW, приходится запихивать в нее синхронизацию, так как многопоточный код пишется в расчете на то, что строка "тупая" — будет копироваться, когда ее передают по значению.
ME>-- ME>Maxim Yegorushkin
А зачем он так пишется? Ну в конце концов, можно сделать две реализации -- одна быстрая, а вторая -- специально для тупо написанного многопоточного кода. Может я слишком много хочу от программистов?
Здравствуйте, Шахтер, Вы писали:
Ш>Да здесь не нужен проект. Скорее -- ФАК, как писать строки, как впрочем и другие контейнеры. Я этого добра написал столько, что теперь всё что мне нужно пишу чуть ли не с закрытими глазами: берётся код из архива, скрещивается, мутируется, дорабатывем напильником -- и готово.
Шахтер:
> ПК>Разве что, в большинстве случаев прикладного программирования это совершенно неэффективно с точки зрения соотношения производительность/стоимость разработки.
> Вы неправильно считаете деньги. Это делается один раз -- хорошо. Или много раз плохо. В рамках написания очередной "экспертной" реализации stl.
Это не хорошо/плохо. Это одним/другим способом. Все зависит от того, какое поведение принять по умолчанию.
> ПК> в большинстве приложений копирование строк и близко не появляется в первой сотне функций, выделенных профайлером.
> А вот я, например, не делаю это большинство. Мне регулярно приходится заниматься задачами, где производительность принципиальна.
Обрати внимание: "копирование строк и близко не появляется в первой сотне функций, выделенных профайлером" и "производительность принципиальна" не являются противопоставлениями.
Posted via RSDN NNTP Server 1.9 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, adontz, Вы писали:
A>Здравствуйте, Шахтер, Вы писали:
Ш>>Да здесь не нужен проект. Скорее -- ФАК, как писать строки, как впрочем и другие контейнеры. Я этого добра написал столько, что теперь всё что мне нужно пишу чуть ли не с закрытими глазами: берётся код из архива, скрещивается, мутируется, дорабатывем напильником -- и готово.
A>Делись!
Знаешь, есть такое слово -- копирайт. Я вот, например, люблю использовать такой немудрящий класс:
Я его написал 14 лет назад, и с тех пор он у меня по всем проектам кочует. А копирайты на этих проектах разные. Вот я и думаю, а не случится ли чего... Особенно, если я буду публично код свой показывать. А если код не такой тривиальный? Засудют ещё. За нарушение авторских прав в цифровую эпоху.
c-smile:
> ПК>...Don't fix what isn't broken...
> А если дизайн broken изначально? Или не подходит к задачам реального мира в 70% случаев?
Мне кажется, с процентами ты немного преувеличил
> Пример: XML/HTML — аттрибуты и их значения повторяются примерно на 70-90% в одном документе. Слепая имплементация в стиле <...> Ведет к неоправданному перерасходу ресурсов и чудовищной неэффективности.
Так не надо слепо, смотреть нужно...
> Попытка имплементации "как должно" (интернирование строк например) средствами стандартной библиотеки приводит к неуклюжим, неэффективным и эмпирически недоказуемым конструкциям.
Во как, но ведь о 70% же доказал
Posted via RSDN NNTP Server 1.9 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Шахтер:
> в конце концов, можно сделать две реализации -- одна быстрая, а вторая -- специально для тупо написанного многопоточного кода. Может я слишком много хочу от программистов?
Скорее, от производителей компиляторов: предоставлять реализацию std::string, неустойчивую к погрешностям проектирования пользовательских приложений, но несколько более эффективную в некоторых частных случаях, для них совершенно невыгодно.
Posted via RSDN NNTP Server 1.9 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Шахтер, Вы писали:
Ш>Здравствуйте, e-Xecutor, Вы писали:
EX>>Здравствуйте, Шахтер, Вы писали:
Ш>>>Это каких, обычных? Если реализация string клонирует строку при копировании, это значит -- кривая реализация.
EX>>Если реализация std::string сделана с подсчётом ссылок, EX>>то отрывать руки надо за такую реализацию.
EX>>Жил был очень многопоточный проект.
Ш>Я извиняюсь, но никаких гарантий касающихся многопоточности стандарт относительно класса string не даёт, так что разработчики библиоткеки вольны делать любую реализацию. А руки надо отрывать людям, которые его бездумно использовали.
Стандарт вообще штука скользкая...
Там много чего недоговорено.
Вот про std::list ничего не сказано про сложность функции size(), дык
некоторые имплементации делают её O(n). Сюрприз еще тот...
ИМХО _нормальная_ реализация stl должна содержать как можно меньше всяких side effect-ов.
то, что после
std::string a="hello";
std::string b=a;
a и b чем-то связаны, это не совсем ожидаемое поведение от строки...
учитывая, что в стандарте ничего про подсчёт ссылок нет,
и как следствие никаких гарантированных средств расклеивания тоже нет...
По крайней мере средств без накладняков...
Здравствуйте, Lexey, Вы писали:
L>Здравствуйте, Шахтер, Вы писали:
Ш>>Я извиняюсь, но никаких гарантий касающихся многопоточности стандарт относительно класса string не даёт, так что разработчики библиоткеки вольны делать любую реализацию. А руки надо отрывать людям, которые его бездумно использовали.
L>Есть такая вещь, как нормальное ожидание поведения объекта. От operator= программист обычно ожидает, что будет создана копия объекта, поскольку именно так ведут себя все встроенные типы. Ты предлагаешь поменять нормальную семантику оператора = и заставить программиста думать на каждом шаге о синхронизации? Как тебе уже сказали, ни к чему кроме трудноуловимых и дорогостоящих глюков это не приведет.
Уважаемые други, а все таки, о какой такой навороченной синхронизации вот уже не первый день идет речь — может не так страшен черт как его малюют?
Что касается производительности строковых операций с частыми модификациями, то для всех уже очевидна необходимость в использовании стринг билдеров, сегодняшний стринг не обладает гибкими стратегиями управления памятью для эффективных частых модификаций.
Will I live tomorrow? Well I just can't say
But I know for sure — I don't live today.
Jimi Hendrix.
Здравствуйте, Batiskaf, Вы писали:
B>Уважаемые други, а все таки, о какой такой навороченной синхронизации вот уже не первый день идет речь — может не так страшен черт как его малюют?
Да вроде как уже все расписали. Если реализовывать подсчет ссылок, то increment, decrement и (что самое фиговое) COW придется синхронизировать внутри самой реализации std:string. Это довольно сильно снизит эффективность.
B>Что касается производительности строковых операций с частыми модификациями, то для всех уже очевидна необходимость в использовании стринг билдеров, сегодняшний стринг не обладает гибкими стратегиями управления памятью для эффективных частых модификаций.
Это да, только не так уж и часто это бывает реально нужно.
Здравствуйте, Lexey, Вы писали:
L>Здравствуйте, Batiskaf, Вы писали:
B>>Уважаемые други, а все таки, о какой такой навороченной синхронизации вот уже не первый день идет речь — может не так страшен черт как его малюют?
L>Да вроде как уже все расписали. Если реализовывать подсчет ссылок, то increment, decrement и (что самое фиговое) COW придется синхронизировать внутри самой реализации std:string. Это довольно сильно снизит эффективность.
Ок, инкремент/декремент это не так дорого, кажется на 86 процессорах есть команда блокирования прерываний, для атомарной операции с каунтерами этого достаточно, на других типах процессоров уверен есть аналоги. И все! Больше ничего синхронизировать не нужно, буфер то все равно ни один экземпляр строки не имеет права модифицировать, каждое модифицирование выливается в построение нового буфера, в этом то прикол COW.
B>>Что касается производительности строковых операций с частыми модификациями, то для всех уже очевидна необходимость в использовании стринг билдеров, сегодняшний стринг не обладает гибкими стратегиями управления памятью для эффективных частых модификаций.
L>Это да, только не так уж и часто это бывает реально нужно.
Ну если строки не модифицируются, значит они чаще всего передаются, использование стратегии COW тем более существенно улучшит перформенс, а хранение стринга одновременно в разных списках еще и приведет к экономичному использованию динамической памяти.
Will I live tomorrow? Well I just can't say
But I know for sure — I don't live today.
Jimi Hendrix.
Здравствуйте, Batiskaf, Вы писали:
L>>Да вроде как уже все расписали. Если реализовывать подсчет ссылок, то increment, decrement и (что самое фиговое) COW придется синхронизировать внутри самой реализации std:string. Это довольно сильно снизит эффективность.
B>Ок, инкремент/декремент это не так дорого, кажется на 86 процессорах есть команда блокирования прерываний, для атомарной операции с каунтерами этого достаточно, на других типах процессоров уверен есть аналоги. И все! Больше ничего синхронизировать B> не нужно, буфер то все равно ни один экземпляр строки не имеет права модифицировать, каждое модифицирование выливается в построение нового буфера, в этом то прикол COW.
Да, насчет COW ты прав. Но InterlockedIncrement/Decrement на самом деле тоже не такая уж и дешевая операция. По Рихтеру порядка 50 тактов.
B>>>Что касается производительности строковых операций с частыми модификациями, то для всех уже очевидна необходимость в использовании стринг билдеров, сегодняшний стринг не обладает гибкими стратегиями управления памятью для эффективных частых модификаций.
L>>Это да, только не так уж и часто это бывает реально нужно. B>Ну если строки не модифицируются, значит они чаще всего передаются, использование стратегии COW тем более существенно улучшит перформенс, а хранение стринга одновременно в разных списках еще и приведет к экономичному использованию динамической памяти.
Если они не модифицируются, то наиболее эффективно их передавать по константной ссылке.
Здравствуйте, e-Xecutor, Вы писали:
EX>>>Жил был очень многопоточный проект.
Ш>>Я извиняюсь, но никаких гарантий касающихся многопоточности стандарт относительно класса string не даёт, так что разработчики библиоткеки вольны делать любую реализацию. А руки надо отрывать людям, которые его бездумно использовали.
И вообще удивительно, как на однопроцессорном ящике это не падало. Или очень сильно везло, или все критические секции были сделаны на interlocked операциях без поддержки многопроцессорности. Так это уже вопросы не к STL.
EX>Стандарт вообще штука скользкая... EX>Там много чего недоговорено. EX>Вот про std::list ничего не сказано про сложность функции size(), дык EX>некоторые имплементации делают её O(n). Сюрприз еще тот... EX>ИМХО _нормальная_ реализация stl должна содержать как можно меньше всяких side effect-ов. EX>то, что после
EX>std::string a="hello";
EX>std::string b=a;
EX>a и b чем-то связаны, это не совсем ожидаемое поведение от строки... EX>учитывая, что в стандарте ничего про подсчёт ссылок нет, EX>и как следствие никаких гарантированных средств расклеивания тоже нет... EX>По крайней мере средств без накладняков...
Накладняк состоит в том, что операции чтения и изменения счётчика ссылок (используемого для Copy-On-Write), во-первых, должны быть, а во-вторых, должны быть interlocked.
Каждое изменение объекта либо получение доступа с правом записи (например, operator[]) выполняет COW. Это, конечно, тягостно.
Поэтому, что действительно было бы полезно — так это явно управлять COW'ом. Например, сделать обёртку вида
template<class T>
class cowboy // парадокс: несколько ковбоев пасут одну корову :))
{
public:
cowboy(); // разделяет общие дефолтные данные
cowboy(const T& data); // клонирует
cowboy(const cowboy& buddy); // разделяет данные с партнёром (если тот не монополист), либо клонирует
cowboy& operator=(const T& data); // клонирует
cowboy& operator=(const cowboy& buddy); // разделяет либо клонирует
~cowboy(); // отпускает (возможно, удаляет)const T& get() const; // ничего не меняетvoid take(); // отпочковывает, заявляет монопольные права
T& unsafe(); // ничего не меняет при условии монопольного права (должен быть предварён вызовом take(), валиден до give())void give(); // снимает монопольные права
// take/give должны быть реентерабельны, поэтому у буфера, помимо счётчика ссылок, есть счётчик монопольности
// если счётчик монопольности == 0, то данные можно разделять
// если счётчик монопольности > 0, то счётчик ссылок == 1 (кроме этого экземпляра, никто буфером не владеет)
// в случае, если забыли снять монопольность, просто будут немедленные клонирования в копикторе и присваивании
// если же забыли назначить монопольность, то при модификации через unsafe() возможно unspecified и даже undefined behavior
// простенькая обёртка для вызоваclass taker
{
cowboy& domain;
public:
taker(cowboy& d) : domain(d) { domain.take(); }
taker(const taker& src) : domain(d) { domain.take(); }
T& get() const { return domain.unsafe(); }
~taker() { domain.give(); }
};
taker safe() { return taker(*this); }
private:
// лень расписывать
};
.....
class Holder
{
cowboy<string> str;
с1() { cout << str.get() << endl; } // константный доступ
с2() { const string& ref = str.get(); cout << ref << endl; } // не нуждается в какой-то рантаймовой защите
m1() { str.take(); str.unsafe() = "a"; str.unsafe() += "b"; str.give(); } // а неконстантный - нуждается
// причём в пределах защиты можно держать ссылки на данные
m2() { str.take(); string& ref = str.unsafe(); ref += "c"; ref += "d"; str.give(); }
// хороший стиль - использовать scope guard
m3() { cowboy<string>::taker t(str); str.unsafe() += "e"; m2(); str.unsafe() += "f"; } // заодно - пример реентера
m4() { str.safe().get() = "g"; } // для однократной защиты можно получить умную ссылку
};
Естественно, злоупотреблять ковбоями не надо. Только там, где действительно здоровенные данные часто передаются по значению.
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>c-smile:
>> ПК>...Don't fix what isn't broken...
>> А если дизайн broken изначально? Или не подходит к задачам реального мира в 70% случаев?
ПК>Мне кажется, с процентами ты немного преувеличил
Да нет, скорее дал оптимистичную оценку.
Из известных мне библиотек/аппликаций интенсивно работающих со строками ( домен: XML/HTML )
я не видел ни одной в которой используется std::string или какая другая имплементация string по принципу std::string.
например mozilla/string использует такие вот абстракции (типы строковых имплементаций):
527 enum
528 {
529 F_NONE = 0, // no flags
530
531 // data flags are in the lower 16-bits
532 F_TERMINATED = 1 << 0, // IsTerminated returns true
533 F_VOIDED = 1 << 1, // IsVoid returns true
534 F_SHARED = 1 << 2, // mData points to a heap-allocated, shared buffer
535 F_OWNED = 1 << 3, // mData points to a heap-allocated, raw buffer
536 F_FIXED = 1 << 4, // mData points to a fixed-size writable, dependent buffer
537
538 // class flags are in the upper 16-bits
539 F_CLASS_FIXED = 1 << 16 // indicates that |this| is of type nsTFixedString
540 };
541
542 //
543 // Some terminology:
544 //
545 // "dependent buffer" A dependent buffer is one that the string class
546 // does not own. The string class relies on some
547 // external code to ensure the lifetime of the
548 // dependent buffer.
549 //
550 // "shared buffer" A shared buffer is one that the string class
551 // allocates. When it allocates a shared string
552 // buffer, it allocates some additional space at
553 // the beginning of the buffer for additional
554 // fields, including a reference count and a
555 // buffer length. See nsStringHeader.
556 //
557 // "adopted buffer" An adopted buffer is a raw string buffer
558 // allocated on the heap (using nsMemory::Alloc)
559 // of which the string class subsumes ownership.
560 //
561 // Some comments about the string flags:
562 //
563 // F_SHARED, F_OWNED, and F_FIXED are all mutually exlusive. They
564 // indicate the allocation type of mData. If none of these flags
565 // are set, then the string buffer is dependent.
566 //
567 // F_SHARED, F_OWNED, or F_FIXED imply F_TERMINATED. This is because
568 // the string classes always allocate null-terminated buffers, and
569 // non-terminated substrings are always dependent.
570 //
571 // F_VOIDED implies F_TERMINATED, and moreover it implies that mData
572 // points to char_traits::sEmptyBuffer. Therefore, F_VOIDED is
573 // mutually exclusive with F_SHARED, F_OWNED, and F_FIXED.
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, e-Xecutor, Вы писали:
EX>>>>Жил был очень многопоточный проект.
Ш>>>Я извиняюсь, но никаких гарантий касающихся многопоточности стандарт относительно класса string не даёт, так что разработчики библиоткеки вольны делать любую реализацию. А руки надо отрывать людям, которые его бездумно использовали.
К>И вообще удивительно, как на однопроцессорном ящике это не падало. Или очень сильно везло, или все критические секции были сделаны на interlocked операциях без поддержки многопроцессорности. Так это уже вопросы не к STL.
Если я правильно понял, там интерлокеды без мембара были.
Но не уверен. В хидерах на тему атомисити чёрт ногу сломит — там
дефайн на дефайне и дефайном погоняет.
А всё, что делалось — из кучи потоков делался запрос, который возвращал _копию_
структуры, в которой были строки. Строка в структуру копировалась из некоего контейнера.
В определённый момент строка в контейнере оказывалась мёртвой.
Хотя она там в рантайме вообще не менялась, заполнялась на взлёте.
И, пардон, греть голову на тему detached copy каждый раз когда
_копия_ строки возвращается в разные потоки — спасибо, такой реализации строк мне не надо...
ИМХО таки если и делать cow строки, то
1) это должно быть опционально
2) по умолчанию отключено.
Я вот сейчас профилированием занимаюсь.
Строковые операции светились где-то более-менее вверху только
когда один нехороший человек делал очень много +=, без предварительного reserve().
Здравствуйте, e-Xecutor, Вы писали:
EX>А всё, что делалось — из кучи потоков делался запрос, который возвращал _копию_ EX>структуры, в которой были строки. Строка в структуру копировалась из некоего контейнера. EX>В определённый момент строка в контейнере оказывалась мёртвой. EX>Хотя она там в рантайме вообще не менялась, заполнялась на взлёте.
EX>И, пардон, греть голову на тему detached copy каждый раз когда EX>_копия_ строки возвращается в разные потоки — спасибо, такой реализации строк мне не надо...
Не, разумеется, детачить нужно только в том месте, где ты собираешься модифицировать.
И, в принципе, грамотный интерлок спасает (как я уже говорил: нужно два счётчика — на количество владельцев и на количество сессий изменения).
EX>ИМХО таки если и делать cow строки, то EX>1) это должно быть опционально EX>2) по умолчанию отключено.
Cow должно работать в масштабе транзакций (сессий изменения), а не отдельных примитивных операций.
Если я поэлементно ковыряю строку (через итераторы или operator[]), то выполнять проверки — неразумная трата времени.
Это касается любых данных, не только векторов и строк.
EX>Я вот сейчас профилированием занимаюсь. EX>Строковые операции светились где-то более-менее вверху только EX>когда один нехороший человек делал очень много +=, без предварительного reserve().
Мы профилировали строки в жёстком режиме ("выжимали кисаньку") и выяснили, что большая часть времени и памяти тратится на выделение буфера. Поэтому разумно делать так: в составе объекта строки держать буфер размера 16-32 символа (оптимум определяется профилированием) и мелочёвку хранить в нём. Немного усложняется метод reserve() — нужно проверять, куда нацелен указатель на данные (в буфере или в куче), и если в буфере — то следить за габаритами.
---
Ш>О том и речь. Классический string из stl не вполне удачно спроектирован. Некоторые напористые люди успели его впаять в стандарт. Как результат -- пользоваться им нельзя. Абзац.
Что-то я не совсем понял, почему?
Используйте STLport std::rope, если нужна строка со счетчиком ссылок, делением на куски и всем таким.
И в стандарт ее обещали добавить.
А std::string так и проектировался, как чистая замена Сишных строк один-к-одному. В них нельзя нормально сделать char &ch = &s[1] без копирования.
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, e-Xecutor, Вы писали:
EX>>А всё, что делалось — из кучи потоков делался запрос, который возвращал _копию_ EX>>структуры, в которой были строки. Строка в структуру копировалась из некоего контейнера. EX>>В определённый момент строка в контейнере оказывалась мёртвой. EX>>Хотя она там в рантайме вообще не менялась, заполнялась на взлёте.
EX>>И, пардон, греть голову на тему detached copy каждый раз когда EX>>_копия_ строки возвращается в разные потоки — спасибо, такой реализации строк мне не надо...
К>Не, разумеется, детачить нужно только в том месте, где ты собираешься модифицировать.
Дык в нашем случае ничего не модифицировалось!
Максимум c_str от неё вызывался.
К>Мы профилировали строки в жёстком режиме ("выжимали кисаньку") и выяснили, что большая часть времени и памяти тратится на выделение буфера. Поэтому разумно делать так: в составе объекта строки держать буфер размера 16-32 символа (оптимум определяется профилированием) и мелочёвку хранить в нём. Немного усложняется метод reserve() — нужно проверять, куда нацелен указатель на данные (в буфере или в куче), и если в буфере — то следить за габаритами.
То бишь примерно так, как в vc 7.x сделано
С такими строками одна проблема. Их нельзя использовать если их нужно много
Но для таких случаев грех спец. класс не написать
Re[14]: Подсчет ссылок в реализации std:
От:
Аноним
Дата:
30.07.04 12:29
Оценка:
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Аноним, Вы писали:
А>>Народ! поделитесь плз, какой редиска нашептал вам на ушко, что киее либо stl контейнеры можно передавать между потоками??? А>>откуда вообще беруться такие бредовые идеи? нравится работать с std::string — замечательно...; нужно передать строку в другой поток — передавай указаль а уже в получающем потоке кидай указатель в string! и даже при таком вареанте нужна синхронизация: ты можеш удалить указатель до того, как он попадет в принимающий поток и скопируется его значение, так что же вообще говорить о контейнерах??? и никакие блокировки, вставленные внутрь контейнера вам никогда не помогут в многопоточной среде, про это надо забыть как про то что можно писать в штанишки!
К>(Сказка произносится тихим, сиплым, страшным голосом — в надежде, что собеседник таки написает в штанишки).
К>Ты или на молоке обжёгся и дуешь на воду, или где-то что-то глубоко недопонял. К>Не говоря уже о фразе "можешь удалить указатель". Это как? delete new LPVOID (some_pointer_value) ?
а как ты что-то передаеш в другой поток?
1)some_type* ptype = new some_type;
2)неким образом post ptype to thread
3)delete ptype;
вот так вот и можно удалить
Здравствуйте, Аноним, Вы писали:
К>>Ты или на молоке обжёгся и дуешь на воду, или где-то что-то глубоко недопонял. К>>Не говоря уже о фразе "можешь удалить указатель". Это как? delete new LPVOID (some_pointer_value) ?
А>а как ты что-то передаеш в другой поток? А>1)some_type* ptype = new some_type; А>2)неким образом post ptype to thread А>3)delete ptype; А>вот так вот и можно удалить
А вот нефиг так делать потому что.
Заводится контейнер, который содержит передаваемые данные. Например, очередь std::queue. Любой доступ к контейнеру — синхронный.
Время жизни данных (и, соответственно, хранилище) — зависит от логики работы. Если поток-отправитель дожидается обработки потоком-приёмником — то можно и на стеке держать (и передавать указатель). Если не дожидается — то в разделяемой памяти (статическое хранилище либо куча), причём заниматься этим может сам контейнер — всё тот же std::queue.
Владение данными — соответственно, у отправителя либо у контейнера.
Перекуём баги на фичи!
Re[16]: Подсчет ссылок в реализации std:
От:
Аноним
Дата:
30.07.04 14:11
Оценка:
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Аноним, Вы писали:
К>>>Ты или на молоке обжёгся и дуешь на воду, или где-то что-то глубоко недопонял. К>>>Не говоря уже о фразе "можешь удалить указатель". Это как? delete new LPVOID (some_pointer_value) ?
А>>а как ты что-то передаеш в другой поток? А>>1)some_type* ptype = new some_type; А>>2)неким образом post ptype to thread А>>3)delete ptype; А>>вот так вот и можно удалить
К>А вот нефиг так делать потому что.
К>Заводится контейнер, который содержит передаваемые данные. Например, очередь std::queue. К>Любой доступ к контейнеру — синхронный. К>Время жизни данных (и, соответственно, хранилище) — зависит от логики работы. Если поток-отправитель дожидается обработки потоком-приёмником — то можно и на стеке держать (и передавать указатель). Если не дожидается — то в разделяемой памяти (статическое хранилище либо куча), причём заниматься этим может сам контейнер — всё тот же std::queue. К>Владение данными — соответственно, у отправителя либо у контейнера.
"те же яйца — вид с боку" какая разница как ты передаеш данные в поток, важно что когда ты их туда получил у тебя есть экземпляр данных и в передающем потоке и в принимающем, соответственно всегда есть возможность изменения данных, когда другой поток этого и не ждет... вообщем то в этом же и проблемма с std::string, которую тут так сильно мусолили... т.е. тыбе в любом случае, если ты конечно хочеш их двух потоков работать с этими данными, нужно организовывать внешнюю синхронизацию, и в этом смысле std::string не отличается ни от чаго другого
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, Кодт, Вы писали:
К>>Здравствуйте, Аноним, Вы писали:
К>>>>Ты или на молоке обжёгся и дуешь на воду, или где-то что-то глубоко недопонял. К>>>>Не говоря уже о фразе "можешь удалить указатель". Это как? delete new LPVOID (some_pointer_value) ?
А>>>а как ты что-то передаеш в другой поток? А>>>1)some_type* ptype = new some_type; А>>>2)неким образом post ptype to thread А>>>3)delete ptype; А>>>вот так вот и можно удалить
К>>А вот нефиг так делать потому что.
К>>Заводится контейнер, который содержит передаваемые данные. Например, очередь std::queue. К>>Любой доступ к контейнеру — синхронный. К>>Время жизни данных (и, соответственно, хранилище) — зависит от логики работы. Если поток-отправитель дожидается обработки потоком-приёмником — то можно и на стеке держать (и передавать указатель). Если не дожидается — то в разделяемой памяти (статическое хранилище либо куча), причём заниматься этим может сам контейнер — всё тот же std::queue. К>>Владение данными — соответственно, у отправителя либо у контейнера.
А>"те же яйца — вид с боку" какая разница как ты передаеш данные в поток, важно что когда ты их туда получил у тебя есть экземпляр данных и в передающем потоке и в принимающем, соответственно всегда есть возможность изменения данных, когда другой поток этого и не ждет... вообщем то в этом же и проблемма с std::string, которую тут так сильно мусолили... т.е. тыбе в любом случае, если ты конечно хочеш их двух потоков работать с этими данными, нужно организовывать внешнюю синхронизацию, и в этом смысле std::string не отличается ни от чаго другого
Секундочку, строки и контейнеры это совершенно разные вещи, то что у стринга есть итераторы еще не значит что строки эквивалентны традиционным спискам, у строк и стратегии владения памятью могут отличаться от контейнеров, я себе мало представляю имплементацию вектора с COW. Так что давайте не смешивать...
Что касается контейнеров и потоков, то они не потокобезопасны не только в STL, и не только в С++, вот например в яве списки облегченные, а через специальную насадку можно получить потокобезопасные контейнеры, если я не ошибаюсь в .Нет-е такой же подход.
Will I live tomorrow? Well I just can't say
But I know for sure — I don't live today.
Jimi Hendrix.
Re[18]: Подсчет ссылок в реализации std:
От:
Аноним
Дата:
03.08.04 06:24
Оценка:
Здравствуйте, Batiskaf, Вы писали:
B>Здравствуйте, Аноним, Вы писали:
А>>Здравствуйте, Кодт, Вы писали:
К>>>Здравствуйте, Аноним, Вы писали:
К>>>>>Ты или на молоке обжёгся и дуешь на воду, или где-то что-то глубоко недопонял. К>>>>>Не говоря уже о фразе "можешь удалить указатель". Это как? delete new LPVOID (some_pointer_value) ?
А>>>>а как ты что-то передаеш в другой поток? А>>>>1)some_type* ptype = new some_type; А>>>>2)неким образом post ptype to thread А>>>>3)delete ptype; А>>>>вот так вот и можно удалить
К>>>А вот нефиг так делать потому что.
К>>>Заводится контейнер, который содержит передаваемые данные. Например, очередь std::queue. К>>>Любой доступ к контейнеру — синхронный. К>>>Время жизни данных (и, соответственно, хранилище) — зависит от логики работы. Если поток-отправитель дожидается обработки потоком-приёмником — то можно и на стеке держать (и передавать указатель). Если не дожидается — то в разделяемой памяти (статическое хранилище либо куча), причём заниматься этим может сам контейнер — всё тот же std::queue. К>>>Владение данными — соответственно, у отправителя либо у контейнера.
А>>"те же яйца — вид с боку" какая разница как ты передаеш данные в поток, важно что когда ты их туда получил у тебя есть экземпляр данных и в передающем потоке и в принимающем, соответственно всегда есть возможность изменения данных, когда другой поток этого и не ждет... вообщем то в этом же и проблемма с std::string, которую тут так сильно мусолили... т.е. тыбе в любом случае, если ты конечно хочеш их двух потоков работать с этими данными, нужно организовывать внешнюю синхронизацию, и в этом смысле std::string не отличается ни от чаго другого
B>Секундочку, строки и контейнеры это совершенно разные вещи, то что у стринга есть итераторы еще не значит что строки эквивалентны традиционным спискам, у строк и стратегии владения памятью могут отличаться от контейнеров, я себе мало представляю имплементацию вектора с COW. Так что давайте не смешивать...
а можно уточнить, в чем разница между строками и контейнерами? только не надо привязываться к списку, контейнеров и других небольшая кучка B>Что касается контейнеров и потоков, то они не потокобезопасны не только в STL, и не только в С++, вот например в яве списки облегченные, а через специальную насадку можно получить потокобезопасные контейнеры, если я не ошибаюсь в .Нет-е такой же подход.
так в том то и дело — любые данные, которые ты передаеш в поток, далжны быть защищены тобой... и невозможно никакими блокировками внутри структуры (контейнера) сделать стректуру потокобезопасной!
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, e-Xecutor, Вы писали:
EX>>Здравствуйте, Кодт, Вы писали:
А>Народ! поделитесь плз, какой редиска нашептал вам на ушко, что киее либо stl контейнеры можно передавать между потоками???
//skipped
Тебя почитать, так вообще передавать данные между потоками нельзя
Обращение к методу getInfo происходит из разных потоков.
Возвращается им (казалось бы) копия строки.
ИМХО реализация по умолчанию должна делать, что бы не казалось.
То бишь по умолчанию копирование должно делать именно копирование.
ИМХО, если хочется каких-то нетривиальных оптимизаций, их надо делать руками,
или пользовать конкретные классы, с конкретным поведением.
B>>Секундочку, строки и контейнеры это совершенно разные вещи, то что у стринга есть итераторы еще не значит что строки эквивалентны традиционным спискам, у строк и стратегии владения памятью могут отличаться от контейнеров, я себе мало представляю имплементацию вектора с COW. Так что давайте не смешивать... А>а можно уточнить, в чем разница между строками и контейнерами? только не надо привязываться к списку, контейнеров и других небольшая кучка
Контейнеры реализуют самые разные стратегии, иногда нужна быстрая вставка, вставка в конец, в начало, иногда сортировка и быстрый поиск, иногда доступ по индексу, либо по ключу — многие из этих манипуляций просто не применимы в отношении строки. Строка в С изначально предполагалась как обычный буфер с минимальными возможностями модификации, то есть предполагалось что для продвинутых строковых операций программисту явно будет недостаточно той функциональности, что предусмотрена стандартом, и он будет сооружать что то своими силами, реализовывать свои стратегии владения памятью, оптимизировать функциональность конкатенации и так далее, жертвовать какими то показателями для достижения хорошей производительности, или напротив, жертвовать производительностью для достижения безопасности времени исполнения.
B>>Что касается контейнеров и потоков, то они не потокобезопасны не только в STL, и не только в С++, вот например в яве списки облегченные, а через специальную насадку можно получить потокобезопасные контейнеры, если я не ошибаюсь в .Нет-е такой же подход. А>так в том то и дело — любые данные, которые ты передаеш в поток, далжны быть защищены тобой... и невозможно никакими блокировками внутри структуры (контейнера) сделать стректуру потокобезопасной!
Никто никому ничего не должен, каждый решает это по своему усмотрению, вот в яве по-моему самое оптимальное решение, предоставить оптимальный список и защищенный, в случае со строками этот подход вполне допустим, иметь строку для минимальных модификаций с синхронизацией, и потоконезащищенный строковый конструктор с продвинутой функциональностью для модификаций.
Will I live tomorrow? Well I just can't say
But I know for sure — I don't live today.
Jimi Hendrix.
Re[14]: Подсчет ссылок в реализации std:
От:
Аноним
Дата:
03.08.04 07:14
Оценка:
Здравствуйте, e-Xecutor, Вы писали:
EX>Здравствуйте, Аноним, Вы писали:
А>>Здравствуйте, e-Xecutor, Вы писали:
EX>>>Здравствуйте, Кодт, Вы писали:
А>>Народ! поделитесь плз, какой редиска нашептал вам на ушко, что киее либо stl контейнеры можно передавать между потоками??? EX>//skipped
EX>Тебя почитать, так вообще передавать данные между потоками нельзя
EX>Еще раз:
EX>
EX>Что не так в этом коде?
EX>Обращение к методу getInfo происходит из разных потоков. EX>Возвращается им (казалось бы) копия строки. EX>ИМХО реализация по умолчанию должна делать, что бы не казалось. EX>То бишь по умолчанию копирование должно делать именно копирование.
EX>ИМХО, если хочется каких-то нетривиальных оптимизаций, их надо делать руками, EX>или пользовать конкретные классы, с конкретным поведением.
Вот твои последнее слова полность передают суть... std::string это конкретный класс, с конкретным поведением, и его поведение таково, что он реализуется посредствоим подсчета ссылок (какая нибудь конкретная реализация, что всегда описано в доке), и если уж очень нужно, то std::string всегда можно заставить скопировать строку, просто съэмитировав запись.
Re[20]: Подсчет ссылок в реализации std:
От:
Аноним
Дата:
03.08.04 07:24
Оценка:
Здравствуйте, Batiskaf, Вы писали:
B>Здравствуйте, Аноним, Вы писали:
B>Контейнеры реализуют самые разные стратегии, иногда нужна быстрая вставка, вставка в конец, в начало, иногда сортировка и быстрый поиск, иногда доступ по индексу, либо по ключу — многие из этих манипуляций просто не применимы в отношении строки.
ну многие из этих операций не применимы и в отношении скажем очереди, ты же при этом не исключаеш очередь из контейнеров
B>Строка в С изначально предполагалась как обычный буфер с минимальными возможностями модификации, то есть предполагалось что для продвинутых строковых операций программисту явно будет недостаточно той функциональности, что предусмотрена стандартом, и он будет сооружать что то своими силами, реализовывать свои стратегии владения памятью, оптимизировать функциональность конкатенации и так далее, жертвовать какими то показателями для достижения хорошей производительности, или напротив, жертвовать производительностью для достижения безопасности времени исполнения.
Даже не только предпологалось, а поскольку накого не устраивала работа с char* и все писали свои классы строк комитет и решил что нужно сделать некий такой общий класс и включить его в стандарт.
B>Никто никому ничего не должен, каждый решает это по своему усмотрению, вот в яве по-моему самое оптимальное решение, предоставить оптимальный список и защищенный, в случае со строками этот подход вполне допустим, иметь строку для минимальных модификаций с синхронизацией, и потоконезащищенный строковый конструктор с продвинутой функциональностью для модификаций.
Здравствуйте, Аноним, Вы писали:
А>Вот твои последнее слова полность передают суть... std::string это конкретный класс, с конкретным поведением, и его поведение таково, что он реализуется посредствоим подсчета ссылок (какая нибудь конкретная реализация, что всегда описано в доке), и если уж очень нужно, то std::string всегда можно заставить скопировать строку, просто съэмитировав запись.
И? Это мне что-нибудь гарантирует? Ничего это не гарантирует.
std::string, это не конкретный класс.
конкретной может быть реализация конкретной библиотеки/компилятора.
В стандарте ничего не написано, что константный метод c_str() может менять внутреннее состояние объекта.
Ладно. Проехали. Я всё равно останусь при своём мнении.