Я пытался оценить, как ссылки на значения влияют на дизайн класса. Скажем, у меня есть существующий класс, как показано ниже
class X
{
string internal;
public:
void set_data(const char* s)
{
internal = s;
}
..
..
..
//other stuff
};
Этот класс используется другим модулем, подобным этому:
//another module
{
string configvalue;
X x;
//read configvalue from a file and call set
...
x.set_data(configvalue.c_str());
//use x to do some magic
..
...}
со ссылками rvalue будет лучше предоставить другую функцию-член, например
class X
{
...
...
....
void set_data(string s)
{
internal = std::move(s);
}
};
Это позволит клиентам этого класса использовать семантику перемещения и предотвратить один набор операций выделения / копирования для каждого использования. Это очень сложный пример, но тот же принцип применим ко всем проектам классов, не нарушая парадигму «минимального интерфейса».
Кто-нибудь понимание по этому вопросу высоко ценится?
Да, добавляя string
перегрузка, как вы предлагаете, это хорошая идея. Даже без ссылок на rvalue такая перегрузка была бы хорошей идеей. В противном случае, учитывая std::string s
Для его использования придется:
x.set_data(s.c_str());
в то время как
x.set_data(s);
гораздо более интуитивно (и даже немного более эффективно) для клиентов X
,
В качестве другого варианта вы можете добавить эти две перегрузки:
void set_data(const string& s) {internal = s;}
void set_data(string&& s) {internal = std::move(s);}
Это примерно эквивалентно той единственной перегрузке, которую вы правильно предложили. У решения с двумя перегрузками есть очень небольшое преимущество в производительности. Решение для одной перегрузки обойдется дополнительно string
переместить конструкцию, когда переданный аргумент является xvalue (lvalue, который был приведен с std::move
). Но ход конструктор std::string
должно быть очень быстро, так что это не должно быть большим делом. Я упоминаю об этом только в духе полного раскрытия.
Если set_data
имеет более одного параметра, подход «по стоимости» становится гораздо более привлекательным. Например рассмотрим случай, когда нужно сдать в два string
s. Ваш выбор:
Решение 1
void set_data(string s1, string s2);
Решение 2
void set_data(const string& s1, const string& s2);
void set_data( string&& s1, const string& s2);
void set_data(const string& s1, string&& s2);
void set_data( string&& s1, string&& s2);
Как вы можете быстро увидеть, Решение 2 плохо масштабируется с количеством параметров.
Наконец, ни в коем случае не пытайтесь применять оба решения к одному и тому же типу:
Не делай этого!
void set_data(string s) {internal = std::move(s);}
void set_data(const string& s) {internal = s;}
void set_data(string&& s) {internal = std::move(s);}
Этот набор перегрузок будет неоднозначным. Как и в C ++ 03, следующие две перегрузки неоднозначны:
void set_data(string s) {internal = std::move(s);}
void set_data(const string& s) {internal = s;}
Никогда не перегружайте значение-значение ссылкой, ни ссылкой на значение, ни ссылкой на значение.
Я не вижу причин, чтобы оба void set_data(const char* s)
а также void set_data(string s)
как часть интерфейса. Это создаст двусмысленность и склонность к побочным эффектам. Более того, вы по-прежнему передаете аргумент по значению в вызове set_data(string s)
, Вместо этого я бы предложил определить 2 следующих функции:
void set_data(const string &s);
void set_data(string &&s);
Таким образом, вы можете иметь 2 реализации, первая будет глубоко копировать вашу строку, а вторая может украсть внутреннюю часть строки, так как это rvalue
(не забудьте оставить его в определенном состоянии, чтобы деструктор мог уничтожить его без проблем — подробности см. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html#Move_Semantics).
Вторая версия будет вызываться автоматически либо на rvalue
string
аргумент или если аргумент вынужден rvalue
например, std::move
,
Если вы хотите иметь опцию по стоимости, вы можете использовать rvalue
версия этого API в сочетании с конструктором копирования строк: set_data(string(str))
,