Дни передачи const std :: string & в качестве параметра истекли?

539

Я слышал недавний разговор Херба Саттера, который предположил, что причины перехода std::vector и std::string на const & в значительной степени исчезли. Он предположил, что предпочтительнее написать функцию, такую ​​как следующее:

std::string do_something ( std::string inval )
{
   std::string return_val;
   // ... do stuff ...
   return return_val;
}

Я понимаю, что return_val будет rvalue в точке, возвращаемой функцией, и поэтому может быть возвращен с использованием семантики перемещения, что очень дешево. Однако inval по-прежнему намного больше размера ссылки (которая обычно реализуется как указатель). Это связано с тем, что std::string имеет различные компоненты, включая указатель на кучу и член char[] для оптимизации коротких строк. Поэтому мне кажется, что переход по ссылке - это хорошая идея.

Может кто-нибудь объяснить, почему Херб мог бы сказать это?

  • 80
    Я думаю, что лучший ответ на этот вопрос, вероятно, - прочитать статью Дэйва Абрахамса об этом на C ++. Далее . Я бы добавил, что не вижу в этом ничего такого, что квалифицируется как не по теме или не конструктивно. Это ясный вопрос о программировании, на который есть фактические ответы.
  • 0
    Отличная ссылка спасибо, я не уверен, почему это становится близко маркеры, как это не по теме для C ++
Показать ещё 16 комментариев
Теги:
c++11

13 ответов

350
Лучший ответ

Причина, по которой Херб сказал, что он сказал, из-за таких случаев.

Скажем, у меня есть функция A, которая вызывает функцию B, которая вызывает функцию C. И A передает строку через B и в C. A не знает и не заботится о C; все A знает о B. То есть C представляет собой деталь реализации B.

Скажем, что A определяется следующим образом:

void A()
{
  B("value");
}

Если B и C берут строку const&, то она выглядит примерно так:

void B(const std::string &str)
{
  C(str);
}

void C(const std::string &str)
{
  //Do something with `str`. Does not store it.
}

Все хорошо и хорошо. Вы просто проделываете указатели, не копируете, не двигаетесь, все счастливы. C принимает значение const&, потому что он не сохраняет строку. Он просто использует его.

Теперь я хочу сделать одно простое изменение: C нужно где-то сохранить строку.

void C(const std::string &str)
{
  //Do something with `str`.
  m_str = str;
}

Здравствуйте, скопируйте конструктор и потенциальное распределение памяти (игнорируйте Оптимизацию коротких строк (SSO)). Предполагается, что семантика перемещения С++ 11 позволяет удалить ненужное копирование, не так ли? И A проходит временный; нет причин, по которым C должен был бы скопировать данные. Он должен просто скрыться от того, что ему было дано.

Кроме того, он не может. Потому что он принимает const&.

Если я изменяю C, чтобы взять его параметр по значению, это просто заставляет B делать копию в этот параметр; Я ничего не получаю.

Итак, если я только что передал str по значению через все функции, полагаясь на std::move, чтобы перетасовать данные вокруг, у нас не было бы этой проблемы. Если кто-то хочет удержать его, они могут. Если они этого не сделают, хорошо.

Это дороже? Да; переход к значению дороже, чем использование ссылок. Это дешевле, чем копия? Не для небольших строк с SSO. Стоит ли делать?

Это зависит от вашего варианта использования. Сколько вы ненавидите выделения памяти?

  • 2
    Когда вы говорите, что перемещение в значение дороже, чем использование ссылок, это все равно дороже на постоянную величину (независимо от длины перемещаемой строки), верно?
  • 0
    @NeilG: Это зависит от того, использует ли стандартная реализация библиотеки оптимизацию маленьких строк, и если да, то каков размер буфера маленьких строк.
Показать ещё 18 комментариев
119

Являются ли дни прохождения const std :: string & в качестве параметра более?

Нет. Многие люди берут этот совет (включая Дейва Абрахама) за пределы домена, к которому он применим, и упрощают его для применения ко всем параметрам std::string Всегда передавать std::string по значению не является "лучшей практикой" для всех и каждого произвольные параметры и приложения, поскольку оптимизация этих обсуждений/статей сосредоточена только на ограниченном наборе случаев.

Если вы возвращаете значение, изменяя параметр или принимая значение, то передача по значению может сэкономить дорогое копирование и предложить синтаксическое удобство.

Как всегда, передача константой const сохраняет много копий, когда вам не нужна копия.

Теперь к конкретному примеру:

Однако inval все еще намного больше, чем размер ссылки (которая обычно реализуется как указатель). Это связано с тем, что std :: string имеет различные компоненты, включая указатель на кучу и член char [] для оптимизации коротких строк. Поэтому мне кажется, что переход по ссылке - это хорошая идея. Может ли кто-нибудь объяснить, почему Херб мог это сказать?

Если размер стека вызывает беспокойство (и предполагая, что это не включено/оптимизировано), return_val + inval > return_val - IOW, использование пикового стека может быть уменьшено путем передачи по значению здесь (обратите внимание: упрощение ABI). Между тем, передача по ссылке const может отключить оптимизацию. Основная причина здесь заключается не в том, чтобы избежать роста стека, а для того, чтобы оптимизация могла быть выполнена там, где это применимо.

Дни прохождения по ссылке const не закончены - правила были сложнее, чем когда-то. Если производительность важна, вам будет разумно рассмотреть, как вы передаете эти типы, на основе сведений, которые вы используете в своих реализациях.

  • 2
    При использовании стека типичные ABI передают одну ссылку в регистр без использования стека.
57

Это сильно зависит от реализации компилятора.

Однако это также зависит от того, что вы используете.

Рассмотрим следующие функции:

bool foo1( const std::string v )
{
  return v.empty();
}
bool foo2( const std::string & v )
{
  return v.empty();
}

Эти функции реализованы в отдельном модуле компиляции, чтобы избежать встраивания. Затем:
1. Если вы передадите литерал к этим двум функциям, вы не увидите большой разницы в характеристиках. В обоих случаях строковый объект должен быть создан
2. Если вы передадите другой объект std::string, foo2 превзойдет foo1, потому что foo1 сделает глубокую копию.

На моем ПК, используя g++ 4.6.1, я получил следующие результаты:

  • переменная по ссылке: 1000000000 итераций → истекшее время: 2.25912 с
  • переменная по значению: 1000000000 итераций → истекшее время: 27.2259 сек
  • литерал по ссылке: 100000000 итераций → время прошло: 9.10319 сек
  • буквальный по значению: 100000000 итераций → время, прошедшее: 8.62659 сек
  • 0
    Конечно, для 2. Вы конкретно имеете в виду строку lvalue. Если функция в основном обрабатывает временные изменения, возвращенные, например, из других функций, то это будет другая история.
  • 4
    Что более важно, так это то, что происходит внутри функции: нужно ли ей при вызове со ссылкой делать внутреннюю копию, которую можно пропустить при передаче по значению ?
Показать ещё 8 комментариев
41

Если вам действительно нужна копия, все равно разумно принять const &. Например:

bool isprint(std::string const &s) {
    return all_of(begin(s),end(s),(bool(*)(char))isprint);
}

Если вы измените это значение, чтобы взять строку по значению, вы в конечном итоге будете перемещать или копировать параметр, и нет необходимости в этом. Скорее всего, не только копирование/перемещение скорее дороже, но и новый потенциальный сбой; копирование/перемещение может вызвать исключение (например, распределение во время копирования может завершиться с ошибкой), тогда как ссылка на существующее значение не может быть.

Если вам нужна копия, тогда передача и возврат по значению обычно (всегда?) - лучший вариант. На самом деле я вообще не стал бы беспокоиться об этом в С++ 03, если вы не обнаружите, что дополнительные копии фактически вызывают проблемы с производительностью. Копирование elision кажется довольно надежным для современных компиляторов. Я думаю, что люди скептицизм и настойчивость в том, что вы должны проверить свою таблицу поддержки компилятора для RVO, в большинстве случаев устарели.


Короче говоря, С++ 11 на самом деле ничего не меняет в этом отношении, кроме людей, которые не доверяют копированию.

  • 1
    Конструкторы перемещения обычно реализуются с помощью noexcept , но конструкторы копирования, очевидно, нет.
40

Короткий ответ: НЕТ! Длинный ответ:

  • Если вы не будете изменять строку (обращение к ней доступно только для чтения), передайте ее как const ref&.
    (const ref&, очевидно, должно оставаться в пределах видимости, пока функция, которая его использует)
  • Если вы планируете изменить его или знаете, что он выйдет из области видимости (потоков), передайте его как value, не скопируйте const ref& внутри вашего тела функции.

На cpp-next.com появилась публикация "Требуется скорость, перейдите по значению!" . TL; DR:

Рекомендация. Не копируйте аргументы функции. Вместо этого передайте их по значению и пусть компилятор выполнит копирование.

ПЕРЕВОД ^

Не копируйте аргументы функции --- означает: если вы планируете изменить значение аргумента, скопировав его во внутреннюю переменную, просто используйте вместо этого аргумент значения.

Итак, не делайте этого:

std::string function(const std::string& aString){
    auto vString(aString);
    vString.clear();
    return vString;
}

сделать это:

std::string function(std::string aString){
    aString.clear();
    return aString;
}

Если вам нужно изменить значение аргумента в своем теле функции.

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

  • 2
    В некоторых случаях рекомендуется передавать по ссылке, но вы указываете на рекомендацию, которая рекомендует всегда передавать по значению.
  • 2
    @KeithThompson Не копируйте аргументы вашей функции. Значит, не копируйте const ref& во внутреннюю переменную, чтобы изменить его. Если вам нужно изменить его ... сделайте параметр значением. Это довольно ясно для моего не говорящего по-английски я.
Показать ещё 7 комментариев
18

Почти.

В С++ 17 у нас есть basic_string_view<?>, basic_string_view<?> сводит нас в основном к одному узкому basic_string_view<?> использования для std::string const& parameters.

Существование хода семантики устранило один случай использования для std::string const& - если вы планируете хранение параметра, принимая std::string по значению является более оптимальным, так как вы можете move из параметра.

Если кто-то назвал вашу функцию необработанной "string" C, это означает, что когда-либо выделяется только один std::string buffer, в отличие от двух в std::string const& case.

Однако, если вы не собираетесь делать копию, взятие std::string const& все еще полезно в С++ 14.

При использовании std::string_view, если вы не передаете указанную строку API, который ожидает символьные буферы C-style '\0' -terminated, вы можете более эффективно получать функции std::string не рискуя распределением. Необработанную строку C можно даже превратить в std::string_view без какого-либо выделения или копирования символов.

В этот момент использование для std::string const& является, когда вы не копируете данные по оптовой цене и собираетесь передать его API-интерфейсу C, который ожидает буфер с нулевым завершением, и вам нужна строка более высокого уровня функции, которые предоставляет std::string. На практике это редкий набор требований.

  • 0
    Я ценю этот ответ, но хочу отметить, что он страдает (как и многие качественные ответы) от некоторого смещения, специфичного для предметной области. То есть: «На практике это редкий набор требований»… в моем собственном опыте разработки эти ограничения, которые кажутся автору необычно узкими, встречаются буквально все время. На это стоит обратить внимание.
  • 1
    @ fish2000 Для ясности, для доминирования std::string вам нужны не только некоторые из этих требований, но и все они. Я признаю, что любой из них или даже два из них являются общими. Может быть, вам обычно нужны все 3 (например, вы делаете какой-то анализ строкового аргумента, чтобы выбрать, на какой C API вы собираетесь передавать его оптом?)
Показать ещё 1 комментарий
16

std::string не Обычные старые данные (POD), а его необработанный размер не самый важный момент. Например, если вы передадите строку, которая превышает длину SSO и выделена в куче, я ожидаю, что конструктор копирования не будет копировать хранилище SSO.

Причина, по которой это рекомендуется, состоит в том, что inval создается из выражения аргумента и поэтому всегда перемещается или копируется по мере необходимости - нет потери производительности, если вы считаете, что вам нужна собственность на аргумент. Если вы этого не сделаете, ссылка const может быть лучшим способом.

  • 2
    Интересный момент в том, что конструктор копирования достаточно умен, чтобы не беспокоиться о едином входе, если он его не использует. Наверное, правильно, мне придется проверить, правда ли это ;-)
  • 3
    @Benj: Старый комментарий, я знаю, но если SSO достаточно мал, его копирование безусловно выполняется быстрее, чем создание условного перехода. Например, 64 байта являются строкой кэша и могут быть скопированы за действительно тривиальное время. Вероятно, 8 циклов или меньше на x86_64.
Показать ещё 2 комментария
15

Я скопировал/ввел ответ из этого вопроса здесь и изменил имена и орфографию, чтобы соответствовать этому вопросу.

Вот код для измерения того, что задается:

#include <iostream>

struct string
{
    string() {}
    string(const string&) {std::cout << "string(const string&)\n";}
    string& operator=(const string&) {std::cout << "string& operator=(const string&)\n";return *this;}
#if (__has_feature(cxx_rvalue_references))
    string(string&&) {std::cout << "string(string&&)\n";}
    string& operator=(string&&) {std::cout << "string& operator=(string&&)\n";return *this;}
#endif

};

#if PROCESS == 1

string
do_something(string inval)
{
    // do stuff
    return inval;
}

#elif PROCESS == 2

string
do_something(const string& inval)
{
    string return_val = inval;
    // do stuff
    return return_val; 
}

#if (__has_feature(cxx_rvalue_references))

string
do_something(string&& inval)
{
    // do stuff
    return std::move(inval);
}

#endif

#endif

string source() {return string();}

int main()
{
    std::cout << "do_something with lvalue:\n\n";
    string x;
    string t = do_something(x);
#if (__has_feature(cxx_rvalue_references))
    std::cout << "\ndo_something with xvalue:\n\n";
    string u = do_something(std::move(x));
#endif
    std::cout << "\ndo_something with prvalue:\n\n";
    string v = do_something(source());
}

Для меня эти выходы:

$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=1 test.cpp
$ a.out
do_something with lvalue:

string(const string&)
string(string&&)

do_something with xvalue:

string(string&&)
string(string&&)

do_something with prvalue:

string(string&&)
$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=2 test.cpp
$ a.out
do_something with lvalue:

string(const string&)

do_something with xvalue:

string(string&&)

do_something with prvalue:

string(string&&)

В приведенной ниже таблице представлены мои результаты (с использованием clang -std = С++ 11). Первое число - это число копий, а второе число - количество перемещаемых конструкций:

+----+--------+--------+---------+
|    | lvalue | xvalue | prvalue |
+----+--------+--------+---------+
| p1 |  1/1   |  0/2   |   0/1   |
+----+--------+--------+---------+
| p2 |  1/0   |  0/1   |   0/1   |
+----+--------+--------+---------+

Решение, основанное на пропуске, требует только одной перегрузки, но требует дополнительной конструкции перемещения при передаче lvalues ​​и xvalues. Это может быть или не быть приемлемым для любой конкретной ситуации. Оба решения имеют преимущества и недостатки.

  • 1
    std :: string - это стандартный библиотечный класс. Это уже и подвижный и копируемый. Я не понимаю, насколько это актуально. ОП спрашивает больше о производительности перемещения и ссылок , а не о производительности перемещения и копирования.
  • 3
    Этот ответ подсчитывает количество ходов и копирует, что std :: string будет проходить в соответствии с схемой передачи по значению, описанной как Хербом, так и Дейвом, по сравнению с передачей по ссылке с парой перегруженных функций. Я использую код OP в демо, за исключением замены в фиктивной строке, чтобы выкрикивать, когда она копируется / перемещается.
Показать ещё 2 комментария
13

Herb Sutter все еще записывается вместе с Bjarne Stroustroup, рекомендуя const std::string& как тип параметра; см. https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rf-in.

В любом из других ответов здесь нет ошибки, если вы передадите строковый литерал в параметр const std::string&, он передаст ссылку на временную строку, созданную "на лету" для хранения символов литерала. Если вы сохраните эту ссылку, она будет недействительной после освобождения временной строки. Чтобы быть в безопасности, вы должны сохранить копию, а не ссылку. Проблема связана с тем, что строковые литералы являются типами const char[N], для которых требуется продвижение до std::string.

Нижеприведенный код иллюстрирует ловушку и обходной путь вместе с небольшим вариантом эффективности - перегрузка с помощью метода const char*, как описано в Есть ли способ передать строку буквально как ссылка в С++.

(Примечание: Sutter и Stroustroup советуют, что если вы сохраняете копию строки, также предоставляете перегруженную функцию с параметром && и std:: move() it.)

#include <string>
#include <iostream>
class WidgetBadRef {
public:
    WidgetBadRef(const std::string& s) : myStrRef(s)  // copy the reference...
    {}

    const std::string& myStrRef;    // might be a reference to a temporary (oops!)
};

class WidgetSafeCopy {
public:
    WidgetSafeCopy(const std::string& s) : myStrCopy(s)
            // constructor for string references; copy the string
    {std::cout << "const std::string& constructor\n";}

    WidgetSafeCopy(const char* cs) : myStrCopy(cs)
            // constructor for string literals (and char arrays);
            // for minor efficiency only;
            // create the std::string directly from the chars
    {std::cout << "const char * constructor\n";}

    const std::string myStrCopy;    // save a copy, not a reference!
};

int main() {
    WidgetBadRef w1("First string");
    WidgetSafeCopy w2("Second string"); // uses the const char* constructor, no temp string
    WidgetSafeCopy w3(w2.myStrCopy);    // uses the String reference constructor
    std::cout << w1.myStrRef << "\n";   // garbage out
    std::cout << w2.myStrCopy << "\n";  // OK
    std::cout << w3.myStrCopy << "\n";  // OK
}

ВЫВОД:

const char * constructor
const std::string& constructor

Second string
Second string
  • 0
    Это другая проблема, и WidgetBadRef не должен иметь const & параметр, чтобы пойти не так. Вопрос в том, что если WidgetSafeCopy просто принимает строковый параметр, будет ли он медленнее? (Я думаю, что копия временная для члена, конечно, легче обнаружить)
  • 0
    Обратите внимание, что T&& не всегда универсальная ссылка; на самом деле, std::string&& всегда будет ссылкой на rvalue и никогда не будет универсальной ссылкой, потому что никакого вывода типа не производится . Таким образом, совет Stroustroup & Sutter не противоречит Мейерсу.
Показать ещё 2 комментария
7

IMO, использующая ссылку С++ для std::string, представляет собой быструю и короткую локальную оптимизацию, в то время как использование передачи по значению может быть (или нет) лучшей глобальной оптимизацией.

Итак, ответ: это зависит от обстоятельств:

  • Если вы пишете весь код извне во внутренние функции, вы знаете, что делает код, вы можете использовать ссылку const std::string &.
  • Если вы пишете библиотечный код или используете сильно библиотечный код, в котором передаются строки, вы, вероятно, получите больше в глобальном смысле, доверяя std::string конструктору поведения конструктора.
3

См. "Herb Sutter" Назад к основам! Essentials of Modern C++ Style ". Среди других тем он рассматривает передаваемые параметры, которые были даны в прошлом, и новые идеи, которые входят в C++ 11, и в частности рассматривает идею передачи строк по значению.

Изображение 1199

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

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

См. Его слайд 27: Для "установленных" функций вариант 1 такой же, как и всегда. Вариант 2 добавляет перегрузку для ссылки rvalue, но это дает комбинаторный взрыв, если имеется несколько параметров.

Только для параметров "sink", где должна быть создана строка (не изменилось ее существующее значение), что трюк "проходка по значению" действителен. То есть, конструкторы, в которых параметр непосредственно инициализирует член типа соответствия.

Если вы хотите увидеть, как глубоко вы можете беспокоиться об этом, посмотрите презентацию Николая Йосуттисса и удачи с этим ("Совершенно-Готово!" N раз после того, как придиреклись к предыдущей версии. Когда-либо там?)


Это также суммируется как ⧺F.15 в Стандартных руководящих принципах.

2

Как отмечает @JDługosz в комментариях, Херб дает другие советы в другом (позже?) Разговоре, см. Примерно отсюда: https://youtu.be/xnqTKD8uD64?t=54m50s.

Его совет сводится к использованию только значений параметров для функции f которая принимает так называемые аргументы погружения, предполагая, что вы переместите конструкцию из этих аргументов поглотителя.

Этот общий подход только добавляет накладные расходы конструктора перемещения для аргументов lvalue и rvalue по сравнению с оптимальной реализацией f учетом значений lvalue и rvalue соответственно. Чтобы понять, почему это так, предположим, что f принимает параметр значения, где T - некоторая копия и перемещает конструктивный тип:

void f(T x) {
  T y{std::move(x)};
}

Вызов f с аргументом lvalue приведет к вызову конструктора копирования для построения x, а конструктор перемещения вызывается для построения y. С другой стороны, вызов f с аргументом rvalue приведет к вызову конструктора перемещения для построения x и другого конструктора перемещения для вызова y.

В общем случае оптимальная реализация f для аргументов lvalue выглядит следующим образом:

void f(const T& x) {
  T y{x};
}

В этом случае для построения y вызывается только один экземпляр. Оптимальная реализация f для аргументов rvalue, в общем случае, выглядит следующим образом:

void f(T&& x) {
  T y{std::move(x)};
}

В этом случае для построения y вызывается только один конструктор перемещения.

Поэтому разумный компромисс заключается в том, чтобы принять параметр значения и иметь один дополнительный вызов конструктора движения для аргументов lvalue или rvalue относительно оптимальной реализации, что также является советом, данным в разговорах Herb.

Как отметил @JDługosz в комментариях, передача по значению имеет смысл только для функций, которые будут строить какой-либо объект из аргумента приемника. Когда у вас есть функция f которая копирует свой аргумент, подход с поправкой будет иметь больше накладных расходов, чем общий метод pass-by-const-reference. Метод pass-by-value для функции f, сохраняющей копию его параметра, будет иметь вид:

void f(T x) {
  T y{...};
  ...
  y = std::move(x);
}

В этом случае есть построение копии и назначение перемещения для аргумента lvalue, а также конструкция перемещения и назначение перемещения для аргумента rvalue. Наиболее оптимальным для аргумента lvalue является:

void f(const T& x) {
  T y{...};
  ...
  y = x;
}

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

Для аргумента rvalue наиболее оптимальная реализация для f, сохраняющая копию, имеет вид:

void f(T&& x) {
  T y{...};
  ...
  y = std::move(x);
}

Таким образом, только перенос в этом случае. Передача rvalue в версию f которая принимает ссылку const, стоит только присваивание вместо назначения перемещения. Так что, условно говоря, предпочтительнее вариант f принимающий const-ссылку в этом случае как общую реализацию.

Поэтому в целом для наиболее оптимальной реализации вам нужно будет перегрузить или сделать какую-то отличную переадресацию, как показано в разговоре. Недостатком является комбинаторный взрыв в количестве требуемых перегрузок, в зависимости от количества параметров для f в случае, если вы решите перегрузить класс значений аргумента. Идеальная пересылка имеет тот недостаток, что f становится функцией шаблона, которая препятствует ее созданию в виртуальном режиме и приводит к значительно более сложному коду, если вы хотите получить его на 100% вправо (см. Разговор о деталях gory).

  • 0
    Посмотрите на выводы Херба Саттера в моем новом ответе: делайте это только тогда, когда вы перемещаете конструкцию , а не перемещаете назначение.
  • 1
    @ JDługosz, спасибо за указатель на выступление Херба, я просто посмотрел его и полностью пересмотрел свой ответ. Я не знал о (переместить) назначить совет.
Показать ещё 1 комментарий
1

Проблема в том, что "const" является неграмотным классификатором. То, что обычно подразумевается под "const string ref", это "не изменять эту строку", а не "не изменять счетчик ссылок". В C++ просто нет способа сказать, какие члены являются "const". Они либо все, либо ни один из них.

Чтобы взломать эту проблему языка, STL может позволить "C()" в вашем примере сделать семантическую копию перемещения в любом случае и покорно игнорировать "const" в отношении счетчика ссылок (изменчивый). Пока это было четко указано, все будет хорошо.

Поскольку STL этого не делает, у меня есть версия строки, которая const_casts <> удаляет ссылочный счетчик (без возможности ретроактивно сделать что-то изменчивым в иерархии классов), и - lo and behold - вы можете свободно передавать cmstring как ссылки на const, и делать копии их в глубоких функциях, в течение всего дня, без утечек или проблем.

Поскольку C++ не предлагает здесь "гранулярность производного класса", написав хорошую спецификацию и создавая блестящий новый объект "const movable string" (cmstring), является лучшим решением, которое я видел.

  • 5
    Ни за что, кроме mutable ключевого слова ...
  • 0
    @BenVoigt да ... вместо отбрасывания он должен быть изменяемым ... но вы не можете изменить член STL на изменяемый в производном классе.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню