c++ x11 и rvalue reference

 
0
 
C++
ava
xTr1m | 25.09.2013, 18:44
День добрый. Просматривая стандарт, увидел такую классную штуку как rvalue reference. Захотел применить, допустим, есть код

CString GetText()
{
CString str = "some big string";
return str;
}

void main()
{
CString str = GetText();
}

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

void main()
{
CString &&str = GetText();
}

ну и еще несколькими дурацкими способами, но не получилось. Конструктор копирования все равно вызывается. Может я что-то не так понял про r-value ref? Спасибо.
Comments (27)
ava
vinter | 25.09.2013, 18:04 #
Покажите CString. У него есть конструктор CString(CString&& rhs)? Если да, то какой компилятор?

added later:
Цитата


Пробовал так


Знаете правило: никогда не брать не-константную ссылку на объект возвращенный из функции? rvalue reference в этом смысле ничем не отличается. Так что так писать весьма вредно.
ava
azesmcar | 25.09.2013, 18:11 #
используй std::move


#include <iostream>
#include <string>

class C
{
public:
    C()
    {
     s_ = "hello world";
    };
    C(C&& c)
    {
     s_ = std::move(c.s_);
    }
    const char *c_str() const
    {
     return s_.c_str();
    }
private:
    std::string s_;
};

int main()
{
    C c1;
    C &&c2 = std::move(c1);

    std::cout << c2.c_str() << std::endl;
}
ava
baldina | 25.09.2013, 18:17 #
xTr1m, ничего вы таким образом не выиграете, даже наоборот. в вашем случае будет NRVO работать.
rvalue reference хорошо применять там, где допускается семантика переноса, не очевидная компилятору (т.е. везде, где не идет речь о создании и возврате временного объекта).
ava
xTr1m | 25.09.2013, 18:24 #
По поводу правила  никогда не брать не-константную ссылку на объект возвращенный из функции это я понимаю, но как я понял rvalue как раз для этого, нет? То есть, был некий объект в функции и  мы хотим его вернуть. Понятно, что возвращать обычную ссылку нельзя, а rvalue не для этого был создан? То есть мы переносим объект из функции в другой объект без копирования, а переносом (указателя или что там еще).

CString - это из MFC и там конечно же нет такого. Делать обертку к нему? Не знаю насколько это будет целесообразно. Может посмотрю в сторону std::string, может там конструктор перемещения определен.

За пример с std::move спасибо. Интересно, а можно использовать его с CString? Но это опять же обертку делать. А вообще как решаются такие задачи. Задача: в функции генерируется большая строка, хочу вернуть ее без перевыделения памяти.
ava
vinter | 25.09.2013, 18:25 #
azesmcar, убери && после C. Твой код конструктор перемещения не вызывает, а лишь создаёт rvalue ссылку.
ava
azesmcar | 25.09.2013, 18:26 #
А вообще можно реализовать Copy-on-write.
ava
xTr1m | 25.09.2013, 18:29 #
Сейчас попробовал сделать так

std::string GetText()
{
std::string str = "some big string";
return str;
}
void main()
{
std::string str = GetText();
}

и тут сразу вызывается конструктор перемещения. попробую засечь насколько быстрее получается.
ava
vinter | 25.09.2013, 18:31 #
Цитата


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


Не совсем. Rvalue reference никогда нельзя возвращать из функции. Это моё утверждение не совсем верно, но пока полностью не проникнитесь rvalue ссылками лучше думать именно так. Т.к. возврщать ссылку на rvalue нужно в очень ограниченном числе случаев. Возвращать нужно просто по значению. Для использования семантики перемещений в возвращаемом объекте код менять не нужно, нужно просто добавить конструктор перемещения  в тот класс, перемещения которго Вы желаете.
Цитата


CString - это из MFC и там конечно же нет такого. Делать обертку к нему? Не знаю насколько это будет целесообразно.


Тем не менее это единственный выход. Другого нет.
Цитата


Может посмотрю в сторону std::string, может там конструктор перемещения определен. 


Да, все библиотечные классы имеют конструктор перемещения(для которых целесообразно, по крайней мере)

Я писал статью про rvalue references. Посмотрите, может проясните что-нибудь для себя.
ava
xTr1m | 25.09.2013, 18:32 #
Большое спасибо, прочту.
ava
xTr1m | 25.09.2013, 19:05 #
Очень странно, написал две функции

std::string GetText()
{
    std::string str("adsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsa");

    return str;
}

CString GetTextMfc()
{
    CString str("adsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsaadsa");

    return str;
}


и прогнал так

for(int i=0; i<10000; ++i)
{
    std::string str = GetText();
    //CString strMfc = GetTextMfc(); // потом наоборот
}

и время выполнения в release одинаково! Хотя в одном случае идет копирование (mfc CString), а в другом перемещение (std::string). Тогда в чем выигрыш в семантике перемещения?
ava
vinter | 25.09.2013, 19:17 #
как выше указал(а) baldina работает NRVO. NRVO быстрее и, соответственно, срабатывает когда можно.
Конструктор перемещения нужен для всех остальных случаев.
ava
xTr1m | 25.09.2013, 19:33 #
То есть NRVO применятеся только в релизе? (спрашиваю, так как в дебаге смотрел и вызывались конструктор копирования и перемещения)
ava
baldina | 25.09.2013, 20:14 #
Цитата (xTr1m @  25.9.2013,  19:33 findReferencedText)
То есть NRVO применятеся только в релизе?

да, потому что в debug отключена оптимизация

Цитата (xTr1m @  25.9.2013,  19:33 findReferencedText)
какой-нибудь пример


class String {
  char *buf;
  size_t size;

  public:
     String (const String& other) {
         delete buf;
         buf = new char[other.size];
         strcpy (buf, other.buf);
         size = other.size;
     }

     String (String&& other) {
         std::swap (buf, other.buf);
         std::swap (size, other.size);
     }

     ~String () { delete buf; }
};
ava
vinter | 25.09.2013, 20:14 #
Цитата


То есть NRVO применятеся только в релизе?


NRVO это дело оптимизатора. Оптимизация, как правило, в дебаге выключена. RVO и NRVO не всегда применимы. Тогда идёт выигрыш от перемещения.
std::unique_ptr еще. Применяется везде где нужно, где есть объект который можно переместить и там где есть объект который только и можно перемещать.
Многие отказываются от передачи по константной ссылке аргументов функции в пользу аргументов по значению, когда передаются типы с перемещающим конструктором. Вариантов масса. Просто искусственно не нужно использовать, я считаю. Ну и взять в привычку добавлять конструктор перемещения для всех своих классов у которых есть явный конструктор копирования.
ava
baldina | 25.09.2013, 20:19 #
кстати, вместо

String (String&& other) {
         std::swap (buf, other.buf);
         std::swap (size, other.size);
}

можно просто

String (String&& other) = default;

ava
vinter | 25.09.2013, 20:22 #
baldina, нельзя, он у тебя просто буфер скопирует побитово и, в результате, будет утечка.
ava
baldina | 25.09.2013, 20:24 #
тут посмотри http://en.cppreference.com/w/cpp/language/move_constructor

added later:
vinter, он побитово скопирует указатель. где утечка?
ava
vinter | 25.09.2013, 20:26 #
предыдущий указатель не удалится
ava
xTr1m | 25.09.2013, 20:34 #
Всем большое спасибо. Пойду еще статью прочитаю, чтобы точно все понять =)) А пока понял, что не нужно применять что бы просто применить (пусть и очень хочется =)) То есть использование достаточно специфическое, и в повседневном коде можно обойтись стандартными инструментами. 
ava
akizelokro | 25.09.2013, 20:44 #
Цитата (xTr1m @  25.9.2013,  17:44 findReferencedText)
 Пробовал так

А так не пробовал?


const CString & cs = GetText();


added later:
Да можно применять. 
ava
xTr1m | 25.09.2013, 20:46 #
Сейчас попробовал. По времени также, как если бы без константной ссылки
ava
baldina | 25.09.2013, 21:12 #
Цитата (vinter @  25.9.2013,  20:26 findReferencedText)
вопрос с невалидным указателем остается

почему он невалидный?
если не использовать явный перенос (т.е. конструктор применяется только к временным объектам), то все в порядке
http://ideone.com/DkFQbw

если использовать явный перенос, надо предотвратить повторное освобождение памяти в деструкторе
http://ideone.com/D4m7KI
ты об этом?
ava
akizelokro | 25.09.2013, 21:15 #
Цитата (xTr1m @  25.9.2013,  20:46 findReferencedText)
Сейчас попробовал. По времени также, как если бы без константной ссылки 


Да не должно меняться при присваивании константной ссылке. В этом случае эффект, что время жизни созданного временного обьекта будет равно времени жизни константной ссылки. Кажись, единственный эффект.
ava
vinter | 26.09.2013, 08:05 #
baldina
Цитата


почему он невалидный?

если не использовать явный перенос (т.е. конструктор применяется только к временным объектам), то все в порядке


Там не всё в порядке. Ты в этом коде так и не вызвал конструктор перемещения. Добавь в тот код еще одну строчку(String c(std::move(a));) и ты увидишь, что указатель удалится 2 раза. Что уже является UB. Но в общем случае до второго удаления есть еще пользование этим объектом, у которого, на деле, указатель уже невалиден.
Мне кажется ты не совсем понимаешь всю это чехарду с перемещением. std::move и конструктор перемещения никогда и ничего не перемещают, если только ты не делаешь этого явно. Чем, по сути, отличается конструктор копирования от конструктора перемещения по умолчанию? первый делает a = rhs.a, а второй a = std::move(rhs.a);. Для композитных объектов  класса у которых нет конструктора перемещения поведение обоих конструкторов по умолчанию идентично. Т.е. в твоём случае это наличие двух объектов, которые ссылаются на один буфер, что есть заезженная проблема из старого доброго C++ к которой прибегают всегда, когда объясняют зачем писать пользовательский конструктор копирования.
Замени char* на std::string и всё будет работать с конструктором перемещения по умолчанию, т.к. std::string "знает" как работать с перемещением.
Твой второй пример есть пример правильного написания конструктора перемещения.
ava
baldina | 26.09.2013, 08:22 #
vinter, так я об этом и написал, только букф поменьше...
ava
vinter | 26.09.2013, 08:33 #
Да нет же. Твой код с String (String&&) = default; неверен. И я объяснил почему.
ava
azesmcar | 26.09.2013, 10:06 #
Цитата (vinter @  25.9.2013,  18:25 findReferencedText)
azesmcar, убери && после C. Твой код конструктор перемещения не вызывает, а лишь создаёт rvalue ссылку. 


ага, спасибо.
Please register or login to write.
Firm of day
Вы также можете добавить свою фирму в каталог IT-фирм, и публиковать статьи, новости, вакансии и другую информацию от имени фирмы.
Подробнее
Contributors
advanced
Submit