rvalue ها در ‪C++11‬

rvalue چیست؟
بر اساس تعریف تو ‪C++11‬ به عبارتی lvalue گفته می‌شه که تو حافظه جایی داره و با عملگر & می‌شه بهش دسترسی داشت. به هر عبارتی که lvalue نباشه rvalue می‌گن. lvalueها می‌تونن هم طرف راست و هم طرف چپ یه عبارت انتساب قرار بگیرن، ولی rvalueها فقط می‌تونن طرف راست باشن.

مثلا اگر a و b و c هر سه از نوع int باشن، عبارت‌های a و b و c از نوع lvalue و عبارت a+b و c-2 و ۲۴ از نوع rvalue هستن. طبیعتا یه فانکشنی که داره از نوع مثلا double برمی‌گردونه، مقدارش از نوع rvalueه (تا حال دیدین بشه نوشت ‪sin(x)=2;‬؟)

به چه درد می‌خوره؟
فرض کنید دارید یه کلاس ماتریس می‌نویسید. در حالت عادی عملگر انتساب شما ماتریس سمت راست رو می‌گیری و به کپی از داده‌ها تو ماتریس سمت چپی ایجاد می‌کنه. حالا برای این کلاسمون به عملگر جمع عم می‌نویسم که این عملگر جمع خودش یه ماتریس حاصل برمی‌گردونه. اگه عملگر مساوی مث سابق عمل بکنه، باید داده‌های حاصل رو سمت چپ تساوی کپی بکنه. تو این حالت ماتریس حاصل جمع اضافه می‌مونه. یه راه بهتر اینه که فقط پویینتر داده‌های ماتریس حاصل جمع کپی بشه. البته این راه حل وقتی خوبه که طرف راست تساوی از نوع rvalue باشه. این جاست که rvalue ها مهم میشن. با داشتن نوع rvalue می‌تونیم یه فانکشن رو سربارگزاری کنیم و بگیم اگه پارامترت rvalue بود یه نوع خاص رفتار بکن. سینتکسش هم که خیلی سادست:
[cpp]
void foo(X& x); // lvalue reference overload
void foo(X&& x); // rvalue reference overload

X x;
X foobar();

foo(x); // argument is lvalue: calls foo(X&)
foo(foobar()); // argument is rvalue: calls foo(X&&)
[/cpp]

بهترین کاربرد این نوع از فانکشن‌ها برای سازنده‌ها و عملگرهای انتسابه. تو این‌جور عملگرهای انتساب، باید آبجکت سمت چپ نابود بشه و رفرنس‌های آبجکت سمت راست به آبجکت سمت چپ منتقل بشن (و نه کپی خونه به خونه). باید توجه کرد تو خود فانکشنی که پارامتر رو گرفته، اون پارامتر به صورت lvalue در نظر گرفته می‌شه.

البته تاکید کنم که از قدیم می‌تونستیم فانکشنمون رو به صورت
[cpp]
void foo(X const &x);
[/cpp]
تعریف کنیم که هم رو lvalueها کار می‌کنه هم رو rvalueها، اما فرقی بینشون نمیذاره.

فانکشن stl::move
این فانکشن یه lvalue رو می‌گیره و به صورت rvlue برمی‌گردونه. با استفاده از این فانکشن، میشه برنامه رو مجبور کرد که با یه متغیر به صورت rvalue کار کنه که به موقعش می‌تونه خیلی کمک کننده باشه.

یه کاربرد مهم این فانکشن تو سازنده‌های کلاس‌های مشتق شدست. مثلا این سازنده رو
[cpp]
Derived(Derived&& rhs)
: Base(rhs) // wrong: rhs is an lvalue
{
// Derived-specific stuff
}
[/cpp]
رو با این یکی
[cpp]
Derived(Derived&& rhs)
: Base(std::move(rhs)) // good, calls Base(Base&& rhs)
{
// Derived-specific stuff
}
[/cpp]
مقایسه کنید. مشخصا سازنده اول غلط عمل می‌کنه.

فراخوانی فانکشن‌ها
یه استفاده مهم دیگه از پارامترهای به شکل rvalue برای جلوگیری از کپی شدن متغیرها موقع پاس دادن اون‌هاست. فرض کنید یه فانکشن مثل این داریم:
[cpp]
template
shared_ptr factory(Arg arg)
{
return shared_ptr(new T(arg));
}
[/cpp]
بدیش اینجاست که این فانکشن یه کپی اضافه از arg موقع پاس دادن به سازنده T می‌سازه و ما می‌خوایم جلوی این رو بگیریم. با استفاده از فانکشن std::forward (که هرکدوم از lvalue و rvalue رو به اون یکی تبدیل می‌کنه) و این سینتکس مشکل حل می‌شه:
[cpp]
template
shared_ptr factory(Arg&& arg)
{
return shared_ptr(new T(std::forward(arg)));
}
[/cpp]

این نوشته خلاصه‌ای از منبع اصلی است.

سازنده ها در c++11، قسمت سوم: مقداردهی یکسان

تو دو تا پست قبل در باره سازنده‌ها در c++11 نوشتم (اینجا و اینجا). این پست آخر از این سریه و به مقداردهی جدید اختصاص داره.

استاندارد جدید برای مقدار دهی اولیه به انواع مختلف، راه حل یکسانی پیشنهاد می‌ده. در واقع مقداردهی اولیه شبیه به حالتی میشه که قبلا برای آرایه‌های با اندازه ثابت استفاده می‌شد:

[cpp light=”true”]
int arr[4]={0,1,2,3};
[/cpp]

اما مقداردهی با این سبک تو استاندارد جدید:

  • آرایه‌های پویا:
    [cpp light=”true”]
    int* a = new int[3] { 1, 2, 0 };
    [/cpp]
  • سازنده‌های کلاس‌ها:
    [cpp light=”true”]
    class C
    {
    int a;
    int b;
    public:
    C(int i, int j);
    }; 

    C c {0,0}; //Equivalent to: C c(0,0);
    [/cpp]

  • مقداردهی اولیه به اعضای آرایه‌ای در سازنده:
    [cpp light=”true”]
    class X {
    int a[4];
    public:
    X() : a{1,2,3,4} {} //C++11, member array initializer
    };
    [/cpp]
  • استفاده از std::initializer_list: اگر پارامتر سازنده از نوع std::initializer_list باشه، سازنده میتونه هر چند تا پارامتر بگیره. به طور خاص این حالت برای کلاس‌هایی که به شکل ذخیر کننده عمل می‌کنند مفیده. مثلا containerهای stl:
    [cpp light=”true”]
    vector vs={ “first”, “second”, “third”};
    map singers =
    { {“Lady Gaga”, “+1 (212) 555-7890”},
    {“Beyonce Knowles”, “+1 (212) 555-0987”}};
    [/cpp]

منابع: اینجا و اینجا

سازنده ها در c++11، قسمت دوم: ارث بری و مقدار دهی اعضا

هفته قبل در مورد محول کردن سازنده های c++11 نوشتم. اینجا میخوام دو تا ویژگی جدید در رابطه با سازنده های c++11 رو معرفی کنم:

  1. به ارث بری سازنده ها: واقعا کفر آدم رو در می آورد. بعد اینکه کلاس فرزند رو به ارث می بری باید بشینی دوباره از اول سازنده هاش رو بازنویسی کنی. خوب این قضیه رو c++11 حل کرده. سینتکسش هم این شکلیه:
    [cpp]
    class BaseClass {
    public:
    BaseClass(int value);
    };

    class DerivedClass : public BaseClass {
    public:
    using BaseClass::BaseClass;
    };
    [/cpp]
    چند تا نکته اما هست که باید دقت بشه:

    • این ویژگی تمام سازنده های کلاس پایه رو با هم به ارث میبره. یعنی نمیشه از بین سازنده ها انتخاب کرد.
    • کلاس فرزند دیگه نمیتونه سازنده ای داشته باشه که امضاش با کلاس مادر یکیه. به نظر من البته این نکته خیلی محدود کنندست.
    • اگه کلاسی همزمان دو تا والد داره، نمیتونه سازنده های جفتشون رو به ارث ببره.
  2. مقدار دهی اولیه به اعضای کلاس
    [cpp]
    class SomeClass {
    int value = 5;
    };
    [/cpp]

سازنده ها در c++11، قسمت اول: محول کردن سازنده ها

اولا بگم که ترجمه کردن constructor delegating کار سختیه :ی
خوب! اصل قضیه اینه که معمولا بخش زیادی از کد سازنده های مختلف کلاسها شبیه هم هستند. این مشکل رو تو ‪C#‬ اینطور حل می کنم که یه متد مخفی مث init میسازم و بخشهای مشترک رو تو اونجا انجام می دم. اما c++03 این اجازه رو نمی داد. اما حالا c++11 این مشکل رو به زیبایی حل میکنه: سازنده های مختلف یک کلاس میتونن همدیگه رو صدا بزنن. به این ترتیب میشه یه سازنده اصلی داشت که سازنده های دیگه همشون اون رو صدا میزنن. اینجوری کد منظم تر و شکیل تر میشه.
اینم مثالش از ویکی پدیا:
[cpp]
class SomeType {
int number;

public:
SomeType(int new_number) : number(new_number) {}
SomeType() : SomeType(42) {}
};
[/cpp]

nullptr

تو ‪c++‬ برای پویینتری که به هیچ جایی اشاره نمی کرد از مقدار ۰ استفاده می شد. ماکرو NULL هم چیزی نبود به غیر از بازتعریف همین مقدار صفر. اما فرض کنید دو تا تابع به شکل زیر داریم:
[cpp]
void f(int);
void f(char *);
[/cpp]
حالا سوال اینه که تو فراخوانی به شکل زیر کدوم تابع باید صدا زده بشه؟
[cpp]
f(0);
[/cpp]
برای رفع این نقطه ابهام c++11 کلمه کلیدی nullptr رو پیشنهاد میکنه. nullptr با همه انواع پویینتر (از جمله پویینتر به تابع) سازگاره. مهمتر از اون اینکه یه راه استاندارد برای تفکیک مقدار دهی اشاره گر با انواع دیگه فراهم میکنه.

منبع