ورود

View Full Version : چگونه از move در c++11 استفاده کنیم؟



asdasd123123
شنبه 03 اسفند 1392, 12:19 عصر
و
.
در چه مواردی باید (یا امکان دارد) از move به جای copy استفاده کنیم؟
.
نحوه ی به کار گیری صحیح این ویژگی جدید چیست؟
.
لطفا مثال بزنید. ممنون.

rahnema1
دوشنبه 05 اسفند 1392, 16:26 عصر
سلام، در خصوص آبجکتها copy و move این جوریه
در Copy یک کپی از شیء درست می شود اما آدرس دو شیء یکسان نمیشه ولی در move یک شیء بطور موقتی درست می شه و شیء دوم که می خواهد از روی اون درست بشه آدرس دو تا شیء یکی می شه
عمل copy و move اشیا به دو طریق انجام میشه یا توسط سازنده کلاس یا از طریق عملگر انتساب = ( که هم عملگر و هم سازنده ای که بخواهد copy یا move کند باید به صورت خاصی تعریف بشه)
به طور خیلی خلاصه و بدون تفصیلات و تبصره ها copy و move توسط سازنده به این صورت هست
الگوی سازنده copy:

Y(Y&)

الگوی سازنده move :

Y(Y&&)

در مثال زیر سه تا سازنده تعریف کردیم که اولی یک سازنده معمولیه دومی سازنده aMove و سومی سازنده copy هست
move به 2 صورت انجام می شه :
در مورد اول آدرس شیء موقتی res که در تابع f ایجاد میشه را چاپ می کنیم
و این شیء موقتی که توسط تابع برگردونده میشه بعنوان پارامتر ورودی سازنده move در می آد و نهایتا آدرس res که دیگه وجود نداره و aMove یکی میشه
مورد دوم Move وقتی صورت می گیره یک شیء موقتی توسط (Y(2 ایجاد شده به عنوان ورودی سازنده move قرار می گیره و به صورت bMove در می آید
copy هم می تونیم به این صورت انجام بدیم:
1.یک شیء ایجاد شده و در c ذخیره می شود و c به عنوان پارامتر ورودی سازنده copy در می آد که نهایتا به dCopy کپی میشه
معمولا اگر عملگر یا سازنده ی مرتبط تعریف نشه عملیات کپی انجام میشه
در Y eCopy = c عملگر انتساب باعث میشه عملیات کپی انجام بشه
به آدرسها دقت کنید در کپی متفاوت ولی در move یکسانند


#include <iostream>
using namespace std;
struct Y
{
int s;
Y(int val){s=val;}
Y(Y&&){};
Y(Y&){};
};
Y f(int val)
{
Y res(val);
cout<<&res<<",";
return res;
}
int main()
{
cout<<"Objects moved:"<<endl;
Y aMove(f(1));
cout<<&aMove<<endl;
Y bMove(Y(2));
cout<<"Objects copied:"<<endl;
Y c(3);
Y dCopy(c);
Y eCopy = c;
cout<<&c<<","<<&dCopy<<","<<&eCopy;
}

omid_kma
شنبه 24 اسفند 1392, 15:20 عصر
عمل copy و move اشیا به دو طریق انجام میشه یا توسط سازنده کلاس یا از طریق عملگر انتساب = ( که هم عملگر و هم سازنده ای که بخواهد copy یا move کند باید به صورت خاصی تعریف بشه
move به شکل های دیگه ای هم تعریف میشه...
برای توابع عضو کلاس توابع معمولی و.. هم میشه از move استفاده کرد .
و این که الگوی سازنده copy constructor به این شکل هم می تونه باشه Y(const Y&)
مورد دوم این که توی کدتون

Y(Y&&){}; Y(Y&){};

اون ; اضافست .

در ضمن برای تعریف move برای constructor به اون شکل عمل نمی کنن راه بهترش استفاده از perfect forwarding و std::forward هستش .

حالا از همه ی اینا هم بگذریم دلیل این که آدرس ها یکی هستن اصلا تعریف move constructor نیست !!
این کد رو ببینید متوجه منظورم میشید اصلا move constructor اجرا نمیشه این جا بر اساس RVO یا return value optimization مقدار Return شده گذاشته میشه داخل struct که کاملا بحثش جدا از move هستش و کوچکترین ربطی هم به move نداره .
#include <iostream>
using namespace std;
struct Y
{
int s;
Y(int val){s=val;}
Y(Y&& a)
{
cout<<"IN MOvE";//chap nemishe
}
Y(Y&){cout<<"IN copy";}
};
Y f(int val)
{
Y res(val);
cout<<&res<<",";
return res;
}
int main()
{
cout<<"Objects moved:"<<endl;
Y aMove(f(1));//aslan move constructor ejra nemishe va RVO in ja anjam mishe
cout<<&aMove<<endl;

}


اگر به این شکل بنویسین از move استفاده میشه که همین طور که میبینین باز هم آدرس ها عوض میشن ..
#include <iostream>
using namespace std;
struct Y
{
int s;
Y(int val){s=val;}
Y(Y&& a):
s(std::move(a.s))//bazam copy anjam mishe
{
cout<<"IN MOvE";
}
Y(Y&){cout<<"IN copy";}
};
Y f(int val)
{
Y res(val);
cout<<&res<<",";
return res;
}
int main()
{
cout<<"Objects moved:"<<endl;
Y aMove(std::move(f(1)));
cout<<&aMove<<endl;

}


در ضمن توی این مثالی که زدین اصلا استفاده از move کاملا بی معنیه چون محتویات داخل struct هم یک int هستش که توی stack ذخیره شده این جا استفاده از move و copy هیچ فرقی با هم ندارن !

برای اطلاعات بیشتر می تونین به این لینک مراجعه کنین :
http://www.7khatcode.com/43/%D9%85%D9%86%D8%B8%D9%88%D8%B1-%D8%A7%D8%B2-move-semantics-%D8%AF%D8%B1-c-11-%DA%86%DB%8C%D9%87-%D8%9F?show=43#q43

rahnema1
شنبه 24 اسفند 1392, 21:26 عصر
سلام، با تشکر از دقت نظر شما



move به شکل های دیگه ای هم تعریف میشه...


من نگفتم به شکل های دیگه تعریف نمیشه و گفتم که در خصوص آبجکت ها به صورت مختصر این جوریه و نیز گفتم به تبصره ها و تفصیلات اون کاری ندارم



و این که الگوری سازنده copy constructor به این شکل هم می تونه باشه
Y(const Y&)


این جوری هم هست
Y(const Y& )
Y(volatile Y& )
Y(const volatile Y& )
Y(const Y&& )
Y(volatile Y&& )
Y(const volatile Y&& )



اون ; اضافست .

بله درسته



در ضمن برای تعریف move برای constructor به اون شکل عمل نمی کنن راه بهترش استفاده از perfect forwarding و std::forward هستش


تا جایی که در منابع مختلف مطالعه کردم روش ایجاد move constructor همین جوریه




حالا از همه ی اینا هم بگذریم دلیل این که آدرس ها یکی هستن اصلا تعریف move constructor نیست !!

درسته این تعریفش نیست فقط برای رساندن مطلب شاید کافی باشه
شاید تعریف بهترش این باشه که حرکت دادن یک شیء سنگین از یک آدرس حافظه به جای دیگر با گرفتن منابع آن در جهت ساختن شیء دوم با کمترین هزینه

منبع:N1377



این کد رو ببینید متوجه منظورم میشید اصلا move constructor اجرا نمیشه


مخالفم اجرا میشه
ببینید با پرینت نمیشه فهمید که این سازنده اجرا شده یا نه بلکه با حذف اون میشه فهمید
وقتی که سازنده حذف شد برنامه خطا میده که به اون سازنده نیاز داره

#include <iostream>
using namespace std;
struct Y
{
int s;
Y(int val){s=val;}
Y(Y&& a)=delete;

Y(Y&){cout<<"IN copy";}
};
Y f(int val)
{
Y res(val);
cout<<&res<<",";
return res;
}
int main()
{
cout<<"Objects moved:"<<endl;
Y aMove(f(1));//aslan move constructor ejra nemishe va RVD in ja anjam mishe
cout<<&aMove<<endl;

}




اگر به این شکل بنویسین از move استفاده میشه که همین طور که میبینین باز هم آدرس ها عوض میشن ..


شما آدرس را عوض کردید خب معلومه
خود به خود که عوض نشده



در ضمن توی این مثالی که زدین اصلا استفاده از move کاملا بی معنیه چون محتویات داخل struct هم یک int هستش که توی stack ذخیره شده این جا استفاده از move و copy هیچ فرقی با هم ندارن !


فقط مثال مختصری بود که جهت بیان این مفهوم در خصوص سازنده کلاسها زدم
به هر منبع و مرجعی شما مراجعه کنید تنها به مثالهای ساده اکتفا میشه تا مفهوم رسونده بشه
من این مثالها را از آخرین ورژن پیش نویس استاندارد 11 گرفتم
جهت اطلاعات بیشتر می تونید به استاندارد و همچنین مقاله ها و پیش نویس های مربوط به آن
مثل N1377 و N1690 و
سایر مقاله های مربوطه مراجعه کنید
با تشکر مجدد از تذکرات شما

omid_kma
شنبه 24 اسفند 1392, 22:58 عصر
تا جایی که در منابع مختلف مطالعه کردم روش ایجاد move constructor همین جوریه
منابعی که مطالعه کردین کامل نبودن .

درسته این تعریفش نیست فقط برای رساندن مطلب شاید کافی باشه
شاید تعریف بهترش این باشه که حرکت دادن یک شیء سنگین از یک آدرس حافظه به جای دیگر با گرفتن منابع آن در جهت ساختن شیء دوم با کمترین هزینه
آره این درسته البته سبک سنگینی هم معنی درستی نمیده وزنه برداری که نمی کنیم :)
ولی در کل این دلیل که نمیشه برای رساندن مطلب اشتباه بنویسیم .

مخالفم اجرا میشه
ببینید با پرینت نمیشه فهمید که این سازنده اجرا شده یا نه بلکه با حذف اون میشه فهمید
وقتی که سازنده حذف شد برنامه خطا میده که به اون سازنده نیاز داره
بله طبیعیه که اگر delete کنیم توی اون مرحله ارور بده چون توی مرحله پارس کامپایلر نمی تونه تصمیم بگیره که آیا واقعا اون نیازه یا نه .. ولی توی مرحله optimize بر اساس RVO دیگه move constructor اجرا نمیشه ( البته بستگی به کامپایلر داره ممکنه اگر از این روش optimize استفاده نکنه اجرا بشه )
در ضمن این اولین باریه که میبینم کسی می گه با پرینت نمیشه فهمید یک تابع اجرا میشه یا نه !

شما آدرس را عوض کردید خب معلومه
خود به خود که عوض نشده
هدف از نوشتن move همینه که آدرس رو جابه جا کنیم move constructor خالی بنویسیم که چی بشه ؟؟

rahnema1
شنبه 24 اسفند 1392, 23:35 عصر
منابعی که مطالعه کردین کامل نبودن


نفی کردن کاری نداره اثبات یه کم مشکله



بله طبیعیه که اگر delete کنیم توی اون مرحله ارور بده چون توی مرحله پارس کامپایلر نمی تونه تصمیم بگیره که آیا واقعا اون نیازه یا نه .. ولی توی مرحله optimize بر اساس RVO دیگه move constructor اجرا نمیشه ( البته بستگی به کامپایلر داره ممکنه اگر از این روش optimize استفاده نکنه اجرا بشه )
در ضمن این اولین باریه که میبینم کسی می گه با پرینت نمیشه فهمید یک تابع اجرا میشه یا نه !

بفرما شما با پرینت نشون بدید کپی میشه



هدف از نوشتن move همینه که آدرس رو جابه جا کنیم move constructor خالی بنویسیم که چی بشه ؟؟

وقتی شما یک شیء موقتی داشته باشی آیا بهتر نیست به جای اینکه این شیء موقتی وقتی می خواهد تحویل داده بشه به جای کپی از منطق move استفاده بشه
معنای تحت اللفظی move به معنای حرکت اشتباه نشه
بیشتر با مفهوم تحویل آدرس سرو کار داریم تا با جابجایی محتوا

omid_kma
یک شنبه 25 اسفند 1392, 10:05 صبح
من حرفی از copy شدن نزدم که بخوام با پرینت چیزی رو نشون بدم . گفتم که move انجام نمیشه . وقتی به اون شکل اول می نویسین آدرس مستقیما میره توی اون یکی شی از struct ولی با روش هایی که compiler کد رو optimize می کنه که ربطی هم به move ندارن قبل از c++11 هم به همین شکل بود .اگر کد اسمبلی برنامتون رو ببینین متوجه میشین چی میگم.
در ضمن نگفتم که محتوا جابه جا بشه گفتم آدرس جا به جا میشه .

rahnema1
یک شنبه 25 اسفند 1392, 10:21 صبح
نه copy انجام میشه نه move انجام میشه پس چه چیزی انجام میشه؟
چرا کامپایلر نمیتونه تصمیم بگیره که از چه سازنده ای استفاده کنه؟ در صورتی که f(1) عبارت است از یک rvalue که طبیعتا rvalue با سازنده move به اصطلاح bind میشه

omid_kma
یک شنبه 25 اسفند 1392, 11:22 صبح
http://stackoverflow.com/questions/7596183/is-rvo-return-value-optimization-applicable-for-all-objects
متن از لینک بالا :
Return Value Optimization can always be applied, what cannot be universally applied is Named Return Value Optimization. Basically, for the optimization to take place, the compiler must know what object is going to be returned at the place where the object is constructed.
In the case of RVO (where a temporary is returned) that condition is trivially met: the object is constructed in the return statement, and well, it is returned.
http://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization
http://en.wikipedia.org/wiki/Return_value_optimization

omid_kma
یک شنبه 25 اسفند 1392, 11:28 صبح
ببین وقتی که توی تابع هستیم کامپایلر نمی تونه تشخیص بده که آیا از این تابع شما زمان ساخت شی استفاده کردین . آیا توی constructor کلاس side effect وجود داره و... برای همین وقتی که move رو delete کنین ارور میده .
ولی وقتی که کامپایلر می خواد کد رو optimize کنه 2 تا انتخاب داره 1_ move constructor رو اجرا کنه . 2_ از rvo استفاده کنه . که توی مثال اولی که زدین از روش دوم استفاده کی کنه (بستگی به کامپایلر هم داره)
حالا به هر حال توی اون کد شما چون آدرسی وجود نداره که جابه جا بشه اگر move هم تعریف کنین با کپی فرقی نداره . (اگر int* میزاشتین move کردن فایده داشت )

rahnema1
یک شنبه 25 اسفند 1392, 14:37 عصر
با تشکردر واقع نه copy اجرا میشه نه move .علاوه بر لینک هایی که شما گذاشتید توی استاندارد بند 12.8 و ردیف 32 گفته در بعضی شرایط در پیاده سازی کامپایلر این اجازه داده شده که به خاطر بهینه کردن علیرغم وجود اثرات جانبی در سازنده ها از copy و move صرفنظر کنه. حالا توی این مورد ما باید move اجرا میشد به خاطر rvalue که به خاطر بهینه کردن از اون استفاده نشده. در مورد اینکه آدرس ها یکی نمیشن حرف شما درست بود. ولی اگه روی مثلا وکتور امتحان کنید آدرس اعضای وکتور جدید با وکتور قبلی که move شده یکی میشه ولی آدرس خود وکتور یکی نمیشه

omid_kma
یک شنبه 25 اسفند 1392, 15:19 عصر
آره چون move در اصل خود شی رو جابه جا نمی کنه شما توی سازنده بهش اعلام کنین که کدوم یکی از فیلد ها رو میخواهید آدرسشو جابه جا کنین
توی وکتور هم یک اشاره گر به یک آرایه هست( T* ) که move روی اون انجام میشه برای همین آدرس اعضا یکی میشن .

omid_kma
سه شنبه 27 اسفند 1392, 01:28 صبح
این یکی جا موند یادم رفت جواب بدم
چون حس می کنم برداشت شما از move اشتباه هستش جواب میدم .



هدف از نوشتن move همینه که آدرس رو جابه جا کنیم move constructor خالی بنویسیم که چی بشه ؟؟ وقتی شما یک شیء موقتی داشته باشی آیا بهتر نیست به جای اینکه این شیء موقتی وقتی می خواهد تحویل داده بشه به جای کپی از منطق move استفاده بشه
معنای تحت اللفظی move به معنای حرکت اشتباه نشه
بیشتر با مفهوم تحویل آدرس سرو کار داریم تا با جابجایی محتوا
بله درسته که بهتره آدرس برای شی های موقت جابه جا بشه ولی
تعریف کردن move constructor خالی باعث نمیشه که move انجام بشه .
همون طوری که تعریف copy constructor خالی باعث نمیشه که چیزی کپی بشه .
شما باید توی move بنویسید که move رو روی کدوم یکی از فیلدهای کلاس می خواهید انجام بدین .
نوشتن move constructor به اون شکلی که شما نوشتید عملا بی فایدست .