Rvalue препратки и клас дизайн

Опитвах се да оценя как препратките към rvalue влияят на дизайна на класа. Да кажем, че имам съществуващ клас, както е показано по-долу

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);
 }
};

Това ще позволи на клиентите от този клас да използват семантика за преместване и ще предотврати един набор от операции за разпределяне/копиране за всяка употреба. Това е силно измислен пример, но същият принцип важи ли за всички дизайни на класове, без да се нарушава парадигмата на „минималния интерфейс“.

Някой прозрения по този въпрос са високо оценени?


person user1751716    schedule 17.10.2012    source източник


Отговори (2)


Да, добавянето на претоварването 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 има повече от един параметър, подходът "по стойност" става много по-привлекателен. Например разгледайте случая, когато трябва да преминете в две strings. Вашият избор е:

Разтвор 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;}

Никога не претоварвайте по-стойност с референция, нито референция lvalue, нито референция rvalue.

person Howard Hinnant    schedule 16.11.2012
comment
void set_data(const string& s) {internal = s;} void set_data(string&& s) {internal = std::move(s);}, кога ще бъде извикан първо и кога второ? - person baye; 17.11.2012
comment
Първата версия ще предпочете да се обвърже с lvalues ​​... втората версия ще предпочете да се свърже с rvalues, както и с lvalues, обвити в std::move. - person Jason; 17.11.2012
comment
По отношение на предаването на множество параметри - да, мащабира се лошо - но това не трябва да е причината да изберете подхода по стойност. Може лесно да бъде решен (и вероятно трябва да бъде от съображения за дизайн) чрез съхраняване на аргументите в един конфигурационен обект, който ще бъде предаден чрез препратка към претоварената функция - намаляване на проблема до оригиналния с един аргумент - person SomeWittyUsername; 17.11.2012

Не виждам причина да има и 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)).

person SomeWittyUsername    schedule 16.11.2012
comment
@HowardHinnant Не. Вие? - person SomeWittyUsername; 16.11.2012
comment
да Когато внедря първите ви две set_data и извикам с rvalue string, компилаторът ми се оплаква от неяснота. Когато добавя вашето 3-то set_data претоварване, също получавам неяснота при извикване с lvalue string. - person Howard Hinnant; 17.11.2012
comment
a, да - защото по стойност. Трябва да се премахне и при нужда може да се постигне същият резултат с другите два и някакъв метод за клониране, напр. - person SomeWittyUsername; 17.11.2012
comment
@HowardHinnant a, да, прав си - поради стойността. Трябва да се премахне и при нужда може да се постигне същия резултат с другите два и някакъв метод на клониране, например. Редактирах отговора. - person SomeWittyUsername; 17.11.2012